├── Pico8Emulator
├── CODEOWNERS
├── .gitignore
├── unit
│ ├── audio
│ │ ├── ChannelData.cs
│ │ ├── PatternData.cs
│ │ ├── PicoNote.cs
│ │ ├── Note.cs
│ │ ├── AudioUnit.cs
│ │ ├── MusicPlayer.cs
│ │ ├── Oscillator.cs
│ │ └── Sfx.cs
│ ├── cart
│ │ ├── RomAddress.cs
│ │ ├── Cartridge.cs
│ │ └── CartridgeUnit.cs
│ ├── Unit.cs
│ ├── graphics
│ │ ├── Palette.cs
│ │ ├── GraphicsUnit.cs
│ │ └── Font.cs
│ ├── mem
│ │ ├── RamAddress.cs
│ │ ├── MemoryUnit.cs
│ │ └── DrawState.cs
│ ├── input
│ │ └── InputUnit.cs
│ └── math
│ │ └── MathUnit.cs
├── backend
│ ├── AudioBackend.cs
│ ├── InputBackend.cs
│ └── GraphicsBackend.cs
├── packages.config
├── todo.md
├── Log.cs
├── MonoGame.Framework.dll.config
├── LICENSE
├── Api.cs
├── Util.cs
├── Properties
│ └── AssemblyInfo.cs
├── lua
│ ├── MoonSharpInterpreter.cs
│ ├── LuaInterpreter.cs
│ └── LuaPatcher.cs
├── Emulator.cs
├── Pico8Emulator.csproj
└── README.md
├── CODEOWNERS
├── .gitignore
├── MonoGamePico8
├── testcarts
│ ├── bounce.p8
│ ├── draw_test.p8
│ ├── btn_test.p8
│ └── jelpi.p8
├── packages.config
├── Program.cs
├── backend
│ ├── MonoGameInputBackend.cs
│ ├── MonoGameAudioBackend.cs
│ └── MonoGameGraphicsBackend.cs
├── FrameCounter.cs
├── Properties
│ └── AssemblyInfo.cs
├── Pico8.cs
└── MonoGamePico8.csproj
├── README.md
├── LICENSE
└── Pico8Emulator.sln
/Pico8Emulator/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @matheusmortatti
2 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @matheusmortatti
2 | * @egordorichev
3 |
--------------------------------------------------------------------------------
/Pico8Emulator/.gitignore:
--------------------------------------------------------------------------------
1 | *.dll
2 | .idea
3 | .git
4 | bin
5 | obj/*
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Lua\ Interpreter/MoonsharpInterpreter.cs
2 | packages/
3 | *obj/
4 | *bin/
5 | *lib/
6 |
--------------------------------------------------------------------------------
/MonoGamePico8/testcarts/bounce.p8:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmortatti/pico8-emulator/HEAD/MonoGamePico8/testcarts/bounce.p8
--------------------------------------------------------------------------------
/Pico8Emulator/unit/audio/ChannelData.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.unit.audio {
2 | public struct ChannelData {
3 | public bool isSilent;
4 | public byte sfxIndex;
5 | }
6 | }
--------------------------------------------------------------------------------
/MonoGamePico8/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Pico8Emulator/backend/AudioBackend.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.backend {
2 | public abstract class AudioBackend {
3 | public Emulator Emulator;
4 |
5 | public abstract void Update();
6 | }
7 | }
--------------------------------------------------------------------------------
/Pico8Emulator/backend/InputBackend.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.backend {
2 | public abstract class InputBackend {
3 | public Emulator Emulator;
4 | public abstract bool IsButtonDown(int i, int p);
5 | }
6 | }
--------------------------------------------------------------------------------
/Pico8Emulator/backend/GraphicsBackend.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.backend {
2 | public abstract class GraphicsBackend {
3 | public Emulator Emulator;
4 |
5 | public abstract void CreateSurface();
6 | public abstract void Flip();
7 | }
8 | }
--------------------------------------------------------------------------------
/Pico8Emulator/unit/audio/PatternData.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.unit.audio {
2 | public struct PatternData {
3 | public ChannelData[] channelCount;
4 | public bool loopStart;
5 | public bool loopEnd;
6 | public bool shouldStop;
7 | }
8 | }
--------------------------------------------------------------------------------
/Pico8Emulator/unit/audio/PicoNote.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.unit.audio {
2 | public struct PicoNote {
3 | public bool isCustom;
4 | public byte effect;
5 | public byte volume;
6 | public byte waveform;
7 | public byte pitch;
8 | }
9 | }
--------------------------------------------------------------------------------
/MonoGamePico8/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MonoGamePico8 {
4 | public class Program {
5 | [STAThread]
6 | public static void Main() {
7 | using (var game = new Pico8()) {
8 | game.Run();
9 | }
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/Pico8Emulator/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Pico8Emulator/todo.md:
--------------------------------------------------------------------------------
1 | # todo
2 |
3 | * better error handling
4 | (print out the piece of code that threw the error)
5 |
6 | * api missing:
7 | exitcmd
8 | assert
9 | stat
10 | stop
11 | trace
12 | mouse/keyboard
13 | update60/update: change fps depending on the specified function
14 | cap frames
15 |
16 | * imeplement menuitem
--------------------------------------------------------------------------------
/Pico8Emulator/Log.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Pico8Emulator {
4 | public static class Log {
5 | public static void Info(object s) {
6 | Console.WriteLine(s);
7 | }
8 |
9 | public static void Error(object s) {
10 | Console.ForegroundColor = ConsoleColor.Red;
11 | Console.WriteLine(s);
12 | Console.ForegroundColor = ConsoleColor.White;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Pico8Emulator/unit/cart/RomAddress.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.unit.cart {
2 | public static class RomAddress {
3 | public const int Gfx = 0x0;
4 | public const int GfxMap = 0x1000;
5 | public const int Map = 0x2000;
6 | public const int GfxProps = 0x3000;
7 | public const int Song = 0x3100;
8 | public const int Sfx = 0x3200;
9 | public const int Meta = 0x8000;
10 | }
11 | }
--------------------------------------------------------------------------------
/Pico8Emulator/unit/cart/Cartridge.cs:
--------------------------------------------------------------------------------
1 | using Pico8Emulator.lua;
2 |
3 | namespace Pico8Emulator.unit.cart {
4 | public class Cartridge {
5 | public const string CartDataPath = "cartdata/";
6 | public const int CartDataSize = 64;
7 | public const int RomSize = 0x8005;
8 |
9 | public byte[] rom = new byte[RomSize];
10 | public string code;
11 | public string path;
12 |
13 | public int[] cartData = new int[CartDataSize];
14 | public string cartDataId = "";
15 |
16 | public LuaInterpreter interpreter;
17 | }
18 | }
--------------------------------------------------------------------------------
/Pico8Emulator/unit/Unit.cs:
--------------------------------------------------------------------------------
1 | using Pico8Emulator.lua;
2 |
3 | namespace Pico8Emulator.unit {
4 | public class Unit {
5 | protected Emulator Emulator;
6 |
7 | public Unit(Emulator emulator) {
8 | Emulator = emulator;
9 | }
10 |
11 | public virtual void DefineApi(LuaInterpreter script) {
12 |
13 | }
14 |
15 | public virtual void Init() {
16 |
17 | }
18 |
19 | public virtual void Destroy() {
20 |
21 | }
22 |
23 | public virtual void Update() {
24 |
25 | }
26 |
27 | public virtual void OnCartridgeLoad() {
28 |
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/MonoGamePico8/backend/MonoGameInputBackend.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xna.Framework.Input;
2 | using Pico8Emulator.backend;
3 | using Pico8Emulator.unit.input;
4 |
5 | namespace MonoGamePico8.backend {
6 | public class MonoGameInputBackend : InputBackend {
7 | private static Keys[] keymap = {
8 | Keys.Left, Keys.Right, Keys.Up, Keys.Down, Keys.X, Keys.Z,
9 | // Second variant
10 | Keys.A, Keys.D, Keys.W, Keys.S, Keys.X, Keys.C
11 | };
12 |
13 | /*
14 | * TODO: implement other player support and gamepad support
15 | */
16 | public override bool IsButtonDown(int i, int p) {
17 | if (p != 0) return false;
18 | var s = Keyboard.GetState();
19 | return s.IsKeyDown(keymap[i]) || s.IsKeyDown(keymap[i + InputUnit.ButtonCount]);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Pico8Emulator/MonoGame.Framework.dll.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Pico8Emulator/unit/graphics/Palette.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Pico8Emulator.unit.graphics {
4 | public static class Palette {
5 | public const int Size = 16;
6 |
7 | public static byte[,] standardPalette = {
8 | { 0, 0, 0 },
9 | { 29, 43, 83 },
10 | { 126, 37, 83 },
11 | { 0, 135, 81 },
12 | { 171, 82, 54 },
13 | { 95, 87, 79 },
14 | { 194, 195, 199 },
15 | { 255, 241, 232 },
16 | { 255, 0, 77 },
17 | { 255, 163, 0 },
18 | { 255, 236, 39 },
19 | { 0, 228, 54 },
20 | { 41, 173, 255 },
21 | { 131, 118, 156 },
22 | { 255, 119, 168 },
23 | { 255, 204, 170 }
24 | };
25 |
26 | public static byte ColorToPalette(Color col) {
27 | for (var i = 0; i < Size; i++) {
28 | if (standardPalette[i, 0] == col.R && standardPalette[i, 1] == col.G && standardPalette[i, 2] == col.B) {
29 | return (byte)i;
30 | }
31 | }
32 |
33 | return 0;
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## PICOEMU
2 |
3 | This is the repository for an implementation of a PICO-8 Emulator!
4 |
5 | PICO-8 is a Fantasy Console created by [Zep](https://twitter.com/lexaloffle). You should [DEFINITELY check it out](https://www.lexaloffle.com/pico-8.php) before using this. PICOEMU in no way, shape or form replaces this wonderful little console, it's super awesome to use and it has a [great community](https://www.lexaloffle.com/bbs/?cat=7).
6 |
7 | This emulator keeps the memory layout but without the CPU and memory limits so that we can expand on PICO-8 games as much as we want to. It is also possible to export games made with PICO-8 to other platforms such as Xbox, PS4 and Switch (given that you have the proper SDK for it).
8 |
9 | If you find a bug, want something to change or would like to add a feature, you can create a bug of send me a message on [twitter](https://twitter.com/MatheusMortatti)! Feel free to create pull requests after the bug or feature is discussed!
--------------------------------------------------------------------------------
/MonoGamePico8/FrameCounter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace MonoGamePico8 {
6 | public class FrameCounter {
7 | public const int MaximumSamples = 30;
8 |
9 | private Queue buffer = new Queue();
10 |
11 | public long TotalFrames { get; private set; }
12 | public float TotalSeconds { get; private set; }
13 | public int AverageFramesPerSecond { get; private set; }
14 | public int CurrentFramesPerSecond { get; private set; }
15 |
16 | public void Update(float deltaTime) {
17 | CurrentFramesPerSecond = (int) Math.Round(1.0f / deltaTime);
18 |
19 | buffer.Enqueue(CurrentFramesPerSecond);
20 |
21 | if (buffer.Count > MaximumSamples) {
22 | buffer.Dequeue();
23 | AverageFramesPerSecond = (int) Math.Round(buffer.Average(i => i));
24 | } else {
25 | AverageFramesPerSecond = CurrentFramesPerSecond;
26 | }
27 |
28 | TotalFrames++;
29 | TotalSeconds += deltaTime;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/MonoGamePico8/testcarts/draw_test.p8:
--------------------------------------------------------------------------------
1 | pico-8 cartridge // http://www.pico-8.com
2 | version 18
3 | __lua__
4 | function _draw()
5 |
6 | cls()
7 |
8 | camera(-10, -10)
9 |
10 | for i=0,400 do
11 | circfill(64, 64, 32)
12 | print(pget(64, 64), 0, 0)
13 | end
14 |
15 | end
16 | __gfx__
17 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
18 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
19 | 00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
20 | 00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
21 | 00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
22 | 00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Matheus Mortatti
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Pico8Emulator/unit/mem/RamAddress.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.unit.mem {
2 | public static class RamAddress {
3 | public const int Gfx = 0x0;
4 | public const int GfxMap = 0x1000;
5 | public const int Map = 0x2000;
6 | public const int GfxProps = 0x3000;
7 | public const int Song = 0x3100;
8 | public const int Sfx = 0x3200;
9 | public const int User = 0x4300;
10 | public const int Cart = 0x5e00;
11 | public const int Palette0 = 0x5f00;
12 | public const int Palette1 = 0x5f10;
13 | public const int ClipLeft = 0x5f20;
14 | public const int ClipTop = 0x5f21;
15 | public const int ClipRight = 0x5f22;
16 | public const int ClipBottom = 0x5f23;
17 | public const int DrawColor = 0x5f25;
18 | public const int CursorX = 0x5f26;
19 | public const int CursorY = 0x5f27;
20 | public const int CameraX = 0x5f28;
21 | public const int CameraY = 0x5f2a;
22 | public const int FillPattern = 0x5f31;
23 | public const int LineX = 0x5f3c;
24 | public const int LineY = 0x5f3e;
25 | public const int ScreenX = 0x5F2C;
26 | public const int ScreenY = 0x5F2C;
27 | public const int Screen = 0x6000;
28 | public const int End = 0x8000;
29 | }
30 | }
--------------------------------------------------------------------------------
/Pico8Emulator/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Matheus Mortatti
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Pico8Emulator/Api.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator {
2 | public static class Api {
3 | public const string All = @"
4 | function all(c)
5 | if c==nil then return function() end end
6 | local i =0
7 | local cn=#c
8 |
9 | return function()
10 | i=i+1
11 | if i<=cn then
12 | return c[i]
13 | end
14 | end
15 | end
16 |
17 | function tostr(x)
18 | if type(x)==""number"" then return tostring(flr(x*10000)/10000) end
19 | return tostring(x)
20 | end
21 |
22 | function tonum(x)
23 | return tonumber(x)
24 | end
25 |
26 | function add(t,v)
27 | if t~=nil then
28 | table.insert(t,v)
29 | end
30 | end
31 |
32 | function del(t,v)
33 | if t~=nil then
34 | for i=0,#t do
35 | if t[i]==v then
36 | table.remove(t,i)
37 | return
38 | end
39 | end
40 | end
41 | end
42 |
43 | function count(a)
44 | return #a
45 | end
46 |
47 | function foreach(t,f)
48 | for e in all(t) do
49 | f(e)
50 | end
51 | end
52 |
53 | local tp=type
54 | function type(f)
55 | if not f then return ""nil"" end
56 | return tp(f)
57 | end
58 |
59 | cocreate=coroutine.create
60 | coresume=coroutine.resume
61 | costatus=coroutine.status
62 | yield=coroutine.yield
63 |
64 | sub=string.sub
65 |
66 | ⬅=0
67 | ➡️=1
68 | ⬆=2
69 | ⬇=3
70 | ❎=4
71 | 🅾️=5";
72 | }
73 | }
--------------------------------------------------------------------------------
/Pico8Emulator/Util.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace Pico8Emulator {
5 | public static class Util {
6 | public const int Shift16 = 1 << 16;
7 |
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static byte GetHalf(byte b, bool rightmost = true) {
10 | if (rightmost) {
11 | return (byte)(b & 0x0f);
12 | } else {
13 | return (byte)((b & 0xf0) >> 4);
14 | }
15 | }
16 |
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | public static void SetHalf(ref byte b, byte val, bool rightmost = true) {
19 | if (rightmost) {
20 | b = (byte)((byte)(b & 0xf0) | (val & 0x0f));
21 | } else {
22 | b = (byte)((byte)(b & 0x0f) | (val << 4));
23 | }
24 | }
25 |
26 | public static int FloatToFixed(double x) {
27 | return (int)(x * Shift16);
28 | }
29 |
30 | public static double FixedToFloat(int x) {
31 | return Math.Round((double)x / Shift16, 4);
32 | }
33 |
34 | public static void Swap(ref T lhs, ref T rhs) {
35 | var temp = lhs;
36 |
37 | lhs = rhs;
38 | rhs = temp;
39 | }
40 |
41 | public static float NoteToFrequency(float note) {
42 | return (float)(440.0 * Math.Pow(2, (note - 33) / 12.0f));
43 | }
44 |
45 | public static float Lerp(float a, float b, float t) {
46 | return (b - a) * t + a;
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/Pico8Emulator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pico8Emulator", "Pico8Emulator\Pico8Emulator.csproj", "{3B26AA2E-0BA2-42DC-AD62-E6C8617F9E4D}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGamePico8", "MonoGamePico8\MonoGamePico8.csproj", "{BA524303-7074-4C9E-8650-59E2C1293A56}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {3B26AA2E-0BA2-42DC-AD62-E6C8617F9E4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {3B26AA2E-0BA2-42DC-AD62-E6C8617F9E4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {3B26AA2E-0BA2-42DC-AD62-E6C8617F9E4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {3B26AA2E-0BA2-42DC-AD62-E6C8617F9E4D}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {BA524303-7074-4C9E-8650-59E2C1293A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {BA524303-7074-4C9E-8650-59E2C1293A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {BA524303-7074-4C9E-8650-59E2C1293A56}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {BA524303-7074-4C9E-8650-59E2C1293A56}.Release|Any CPU.Build.0 = Release|Any CPU
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/MonoGamePico8/backend/MonoGameAudioBackend.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Xna.Framework.Audio;
3 | using Pico8Emulator.backend;
4 | using Pico8Emulator.unit.audio;
5 |
6 | namespace MonoGamePico8.backend {
7 | public class MonoGameAudioBackend : AudioBackend {
8 | private DynamicSoundEffectInstance soundInstance;
9 |
10 | public MonoGameAudioBackend() {
11 | soundInstance = new DynamicSoundEffectInstance(AudioUnit.SampleRate, AudioChannels.Mono);
12 | soundInstance.Play();
13 | }
14 |
15 | public override void Update() {
16 | while (soundInstance.PendingBufferCount < 3) {
17 | var p8Buffer = Emulator.Audio.RequestBuffer();
18 | var samplesPerBuffer = p8Buffer.Length;
19 | var audioBuffer = new byte[samplesPerBuffer * 2];
20 |
21 | for (var i = 0; i < samplesPerBuffer; i += 1) {
22 | var floatSample = p8Buffer[i];
23 |
24 | var shortSample =
25 | (short) (floatSample >= 0.0f ? floatSample * short.MaxValue : floatSample * short.MinValue * -1);
26 |
27 | if (!BitConverter.IsLittleEndian) {
28 | audioBuffer[i * 2] = (byte) (shortSample >> 8);
29 | audioBuffer[i * 2 + 1] = (byte) shortSample;
30 | } else {
31 | audioBuffer[i * 2] = (byte) shortSample;
32 | audioBuffer[i * 2 + 1] = (byte) (shortSample >> 8);
33 | }
34 | }
35 |
36 | soundInstance.SubmitBuffer(audioBuffer);
37 | }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/MonoGamePico8/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("MonoGamePico8")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("MonoGamePico8")]
12 | [assembly: AssemblyCopyright("Copyright © 2019")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("BA524303-7074-4C9E-8650-59E2C1293A56")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/Pico8Emulator/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("Pico8Emulator")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("Pico8Emulator")]
12 | [assembly: AssemblyCopyright("Copyright © 2019")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("EC0602B1-5A17-4CDC-BBB7-F2C5401FF899")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/Pico8Emulator/lua/MoonSharpInterpreter.cs:
--------------------------------------------------------------------------------
1 | using MoonSharp.Interpreter;
2 | using System;
3 |
4 | namespace Pico8Emulator.lua {
5 | public class MoonSharpInterpreter : LuaInterpreter {
6 | private Script script = new Script();
7 | private string latestScript;
8 |
9 | public void AddFunction(string name, object func) {
10 | script.Globals[name] = func;
11 | }
12 |
13 | public void CallFunction(string name) {
14 | try {
15 | script.Call(name);
16 | }
17 | catch (Exception e) {
18 | HandleError(e, name);
19 | }
20 | }
21 |
22 | public bool CallIfDefined(string name) {
23 | if (IsDefined(name)) {
24 | try {
25 | script.Call(script.Globals[name]);
26 | }
27 | catch (Exception e) {
28 | HandleError(e, name);
29 | }
30 |
31 | return true;
32 | }
33 |
34 | return false;
35 | }
36 |
37 | public void RunScript(string str) {
38 | try {
39 | latestScript = str;
40 | script.DoString(str);
41 | }
42 | catch (Exception e) {
43 | HandleError(e, "runscript()");
44 | }
45 | }
46 |
47 | private void HandleError(Exception e, string where) {
48 | // Uncomment for debugging. Commented out to save my ssd
49 | // File.WriteAllText("log.txt", latestScript);
50 |
51 | if (e is MoonSharp.Interpreter.SyntaxErrorException se) {
52 | Log.Error($"{@where}: {se.DecoratedMessage}");
53 | }
54 | else if (e is MoonSharp.Interpreter.InterpreterException ie) {
55 | Log.Error($"{@where}: {ie.DecoratedMessage}");
56 | }
57 | }
58 |
59 | public bool IsDefined(string name) {
60 | return script.Globals[name] != null;
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/MonoGamePico8/testcarts/btn_test.p8:
--------------------------------------------------------------------------------
1 | pico-8 cartridge // http://www.pico-8.com
2 | version 18
3 | __lua__
4 | function _draw()
5 |
6 | cls()
7 |
8 | print("btn(0): " .. tostr(btn(0)), 0, 0, 7)
9 | print("btn(1): " .. tostr(btn(1)), 0, 8, 7)
10 | print("btn(2): " .. tostr(btn(2)), 0, 16, 7)
11 | print("btn(3): " .. tostr(btn(3)), 0, 24, 7)
12 | print("btn(4): " .. tostr(btn(4)), 0, 32, 7)
13 | print("btn(5): " .. tostr(btn(5)), 0, 40, 7)
14 |
15 | print("btnp(0): " .. tostr(btnp(0)), 64, 0, 9)
16 | print("btnp(1): " .. tostr(btnp(1)), 64, 8, 9)
17 | print("btnp(2): " .. tostr(btnp(2)), 64, 16, 9)
18 | print("btnp(3): " .. tostr(btnp(3)), 64, 24, 9)
19 | print("btnp(4): " .. tostr(btnp(4)), 64, 32, 9)
20 | print("btnp(5): " .. tostr(btnp(5)), 64, 40, 9)
21 |
22 | print("btn mask: " .. tostr(btn()), 0, 64, 7)
23 | print("btnp mask: " .. tostr(btnp()), 64, 64, 9)
24 |
25 | end
26 | __gfx__
27 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
28 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
29 | 00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
30 | 00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
31 | 00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
32 | 00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
33 |
--------------------------------------------------------------------------------
/MonoGamePico8/backend/MonoGameGraphicsBackend.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Microsoft.Xna.Framework;
4 | using Microsoft.Xna.Framework.Graphics;
5 | using Pico8Emulator;
6 | using Pico8Emulator.backend;
7 | using Pico8Emulator.unit.graphics;
8 | using Pico8Emulator.unit.mem;
9 |
10 | namespace MonoGamePico8.backend {
11 | public class MonoGameGraphicsBackend : GraphicsBackend {
12 | public Texture2D Surface;
13 |
14 | private GraphicsDevice graphics;
15 | private Color[] screenColorData = new Color[GraphicsUnit.ScreenSize];
16 | private Color[] palette;
17 |
18 | public MonoGameGraphicsBackend(GraphicsDevice graphicsDevice) {
19 | graphics = graphicsDevice;
20 | palette = new Color[Palette.Size];
21 |
22 | for (var i = 0; i < Palette.Size; i++) {
23 | palette[i] = new Color(Palette.standardPalette[i, 0], Palette.standardPalette[i, 1], Palette.standardPalette[i, 2]);
24 | }
25 | }
26 |
27 | public override void CreateSurface() {
28 | Surface = new Texture2D(graphics, 128, 128, false, SurfaceFormat.Color);
29 | }
30 |
31 | public override void Flip() {
32 | var ram = Emulator.Memory.ram;
33 | var drawState = Emulator.Memory.drawState;
34 |
35 | for (var i = 0; i < 8192; i++) {
36 | var val = ram[i + RamAddress.Screen];
37 |
38 | screenColorData[i * 2] = palette[drawState.GetScreenColor(val & 0x0f)];
39 | screenColorData[i * 2 + 1] = palette[drawState.GetScreenColor(val >> 4)];
40 | }
41 |
42 | Surface.SetData(screenColorData);
43 | }
44 |
45 | private byte ColorToPalette(Color col) {
46 | for (var i = 0; i < 16; i += 1) {
47 | if (palette[i] == col) {
48 | return (byte) i;
49 | }
50 | }
51 |
52 | return 0;
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/Pico8Emulator/lua/LuaInterpreter.cs:
--------------------------------------------------------------------------------
1 | namespace Pico8Emulator.lua {
2 | ///
3 | /// Defines the interface for a lua interpreter like Moonsharp or NLua.
4 | ///
5 | public interface LuaInterpreter {
6 | ///
7 | /// Function to add a C# function to the lua interpreter so that lua code can call it.
8 | ///
9 | /// The name of the function.
10 | /// The function to be added.
11 | void AddFunction(string name, object func);
12 |
13 | ///
14 | /// Calls a function that is present in the interpreter's function list. Can call a non existing function, getting a nullptr exception.
15 | ///
16 | /// The name of the function to be called.
17 | void CallFunction(string name);
18 |
19 | ///
20 | /// Calls a function that is present in the interpreter's function list. Only called if the function exists.
21 | ///
22 | /// The name of the function to be called.
23 | /// True if the function exists, false otherwise.
24 | bool CallIfDefined(string name);
25 |
26 | ///
27 | /// Runs a lua script with the interpreter that implemented this interface.
28 | ///
29 | /// The script to be ran.
30 | void RunScript(string script);
31 |
32 | ///
33 | /// Checks if the function name given is defined inside the interpreter.
34 | ///
35 | /// The name of the function to catch.
36 | /// True if the function is defined, false otherwise..
37 | bool IsDefined(string name);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Pico8Emulator/unit/input/InputUnit.cs:
--------------------------------------------------------------------------------
1 | using Pico8Emulator.lua;
2 | using System;
3 |
4 | namespace Pico8Emulator.unit.input {
5 | /*
6 | * TODO: I removed mask implementation for simplicity, get it back!
7 | */
8 | public class InputUnit : Unit {
9 | public const int ButtonCount = 6;
10 | public const int MaxPlayers = 8;
11 | public const int StateSize = ButtonCount * MaxPlayers;
12 |
13 | private bool[] _previousButtonState = new bool[StateSize];
14 | private bool[] _currentButtonState = new bool[StateSize];
15 |
16 | public InputUnit(Emulator emulator) : base(emulator) {
17 |
18 | }
19 |
20 | public override void DefineApi(LuaInterpreter script) {
21 | base.DefineApi(script);
22 |
23 | script.AddFunction("btn", (Func)Btn);
24 | script.AddFunction("btnp", (Func)Btnp);
25 | }
26 |
27 | public override void Update() {
28 | base.Update();
29 |
30 | for (var i = 0; i < ButtonCount; ++i) {
31 | for (var p = 0; p < MaxPlayers; ++p) {
32 | var index = ToIndex(i, p);
33 | _previousButtonState[index] = _currentButtonState[index];
34 | _currentButtonState[index] = Emulator.InputBackend.IsButtonDown(i, p);
35 | }
36 | }
37 | }
38 |
39 | private static int ToIndex(int? i, int? p) {
40 | return (i ?? 0) + ((p ?? 0) * ButtonCount);
41 | }
42 |
43 | public object Btn(int? i = null, int? p = null) {
44 | if (i == null) {
45 | int bitMask = 0;
46 | for (int k = 0; k < ButtonCount; ++k) {
47 | for (int j = 0; j < 2; ++j) {
48 | bitMask |= ((_currentButtonState[ToIndex(k, j)] ? 1 : 0) << (ButtonCount * j + k));
49 | }
50 | }
51 | return bitMask;
52 | }
53 |
54 | return _currentButtonState[ToIndex(i, p)];
55 | }
56 |
57 | public object Btnp(int? i = null, int? p = null) {
58 | int index = 0;
59 |
60 | if (i == null) {
61 | int bitMask = 0;
62 | for (int k = 0; k < ButtonCount; ++k) {
63 | for (int j = 0; j < 2; ++j) {
64 | index = ToIndex(k, j);
65 | bitMask |= ((_currentButtonState[index] && !_previousButtonState[index] ? 1 : 0) << (ButtonCount * j + k));
66 | }
67 | }
68 | return bitMask;
69 | }
70 |
71 | index = ToIndex(i, p);
72 | return _currentButtonState[index] && !_previousButtonState[index];
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/Pico8Emulator/unit/audio/Note.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Pico8Emulator.unit.audio {
4 | public class Note {
5 | private float[] _audioBuffer;
6 | private float _duration;
7 | private float _fadeIn;
8 | private float _fadeOut;
9 | private float _timePassed;
10 | private int _sampleRate;
11 | public bool isCustom;
12 | public float targetVolume;
13 | public byte waveform;
14 | public byte pitch;
15 |
16 | private bool _vibrato;
17 | private float _volume;
18 | private int _pitchFrom;
19 | private Oscillator _oscillator;
20 |
21 | public Note(ref float[] audioBuffer, int sampleRate, ref Oscillator oscillator, float duration, byte volume,
22 | byte waveform, byte pitch, int pitchFrom = -1, float fadeIn = 1, float fadeOut = 1, bool vibrato = false) {
23 | _audioBuffer = audioBuffer;
24 |
25 | _duration = duration;
26 | _sampleRate = sampleRate;
27 |
28 | _fadeIn = fadeIn * duration / 100.0f;
29 | _fadeOut = fadeOut * duration / 100.0f;
30 |
31 | _timePassed = 0.0f;
32 | _volume = 1;
33 |
34 | isCustom = waveform > 7;
35 | targetVolume = volume / 7.0f;
36 | this.waveform = waveform;
37 | this.pitch = pitch;
38 |
39 | _pitchFrom = pitchFrom == -1 ? pitch : pitchFrom;
40 |
41 | _oscillator = oscillator;
42 | _vibrato = vibrato;
43 | }
44 |
45 | public int Process(int bufferOffset = 0, bool writeToBuffer = true) {
46 | for (int i = bufferOffset; i < AudioUnit.BufferSize; i++) {
47 | if (writeToBuffer) {
48 | if (_timePassed < _fadeIn) {
49 | _volume = Util.Lerp(0, targetVolume, _timePassed / _fadeIn);
50 | }
51 | else if (_timePassed > _duration - _fadeOut) {
52 | _volume = Util.Lerp(targetVolume, 0, (_timePassed - (_duration - _fadeOut)) / _fadeOut);
53 | }
54 | else {
55 | _volume = targetVolume;
56 | }
57 |
58 | if (_timePassed >= _duration) {
59 | return i;
60 | }
61 |
62 | float freq;
63 |
64 | if (_vibrato) {
65 | freq = Util.Lerp(Util.NoteToFrequency(pitch), Util.NoteToFrequency(pitch + 0.5f),
66 | (float)Math.Sin(_timePassed * 2 * Math.PI * 8));
67 | }
68 | else {
69 | freq = Util.Lerp(Util.NoteToFrequency(_pitchFrom), Util.NoteToFrequency(pitch), _timePassed / _duration);
70 | }
71 |
72 | float sample = _oscillator.waveFuncMap[waveform](freq);
73 | _audioBuffer[i] += sample * _volume;
74 | }
75 |
76 | _timePassed += 1.0f / _sampleRate;
77 | }
78 |
79 | return AudioUnit.BufferSize;
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/Pico8Emulator/Emulator.cs:
--------------------------------------------------------------------------------
1 | using Pico8Emulator.backend;
2 | using Pico8Emulator.lua;
3 | using Pico8Emulator.unit;
4 | using Pico8Emulator.unit.audio;
5 | using Pico8Emulator.unit.cart;
6 | using Pico8Emulator.unit.input;
7 | using Pico8Emulator.unit.math;
8 | using Pico8Emulator.unit.mem;
9 | using System;
10 | using System.Collections.Generic;
11 | using GraphicsUnit = Pico8Emulator.unit.graphics.GraphicsUnit;
12 |
13 | namespace Pico8Emulator {
14 | public class Emulator {
15 | public readonly List units = new List();
16 |
17 | public MemoryUnit Memory;
18 | public GraphicsUnit Graphics;
19 | public AudioUnit Audio;
20 | public MathUnit Math;
21 | public InputUnit Input;
22 | public CartridgeUnit CartridgeLoader;
23 |
24 | public readonly GraphicsBackend GraphicsBackend;
25 | public readonly AudioBackend AudioBackend;
26 | public readonly InputBackend InputBackend;
27 |
28 | public Emulator(GraphicsBackend graphics, AudioBackend audio, InputBackend input) {
29 | GraphicsBackend = graphics;
30 | AudioBackend = audio;
31 | InputBackend = input;
32 |
33 | graphics.Emulator = this;
34 | audio.Emulator = this;
35 | input.Emulator = this;
36 |
37 | units.Add(Memory = new MemoryUnit(this));
38 | units.Add(Graphics = new GraphicsUnit(this));
39 | units.Add(Audio = new AudioUnit(this));
40 | units.Add(Math = new MathUnit(this));
41 | units.Add(Input = new InputUnit(this));
42 | units.Add(CartridgeLoader = new CartridgeUnit(this));
43 |
44 | foreach (var unit in units) {
45 | unit.Init();
46 | }
47 | }
48 |
49 | public void Shutdown() {
50 | foreach (var unit in units) {
51 | unit.Destroy();
52 | }
53 | }
54 |
55 | public void Update30() {
56 | CartridgeLoader.Update30();
57 | }
58 |
59 | public void Update60() {
60 | foreach (var unit in units) {
61 | unit.Update();
62 | }
63 | }
64 |
65 | public void Draw() {
66 | CartridgeLoader.Draw();
67 | }
68 |
69 | public void InitApi(LuaInterpreter script) {
70 | foreach (var unit in units) {
71 | unit.DefineApi(script);
72 | }
73 |
74 | script.AddFunction("printh", (Action