├── 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)Printh); 75 | script.AddFunction("stat", (Func)Stat); 76 | script.AddFunction("menuitem", (Action)Menuitem); 77 | 78 | script.RunScript(LuaPatcher.PatchCode(Api.All)); 79 | } 80 | 81 | public void Menuitem(int index, string label = null, object callback = null) { 82 | // TODO: implement 83 | } 84 | 85 | public void Printh(object s) { 86 | Console.WriteLine($"{s:####.####}"); 87 | } 88 | 89 | public object Stat() { 90 | return 0; // TODO: implement 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /MonoGamePico8/Pico8.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using MonoGamePico8.backend; 5 | using Pico8Emulator; 6 | using System; 7 | using System.Diagnostics; 8 | 9 | namespace MonoGamePico8 { 10 | public class Pico8 : Game { 11 | private const float UpdateTime30 = 1 / 30f; 12 | private const float UpdateTime60 = 1 / 60f; 13 | 14 | private Emulator _emulator; 15 | private GraphicsDeviceManager _graphics; 16 | private SpriteBatch _batch; 17 | private FrameCounter _counter; 18 | private MonoGameGraphicsBackend _graphicsBackend; 19 | private float _deltaUpdate30, _deltaUpdate60, _deltaDraw; 20 | 21 | public Pico8() { 22 | _graphics = new GraphicsDeviceManager(this); 23 | _counter = new FrameCounter(); 24 | 25 | IsFixedTimeStep = false; 26 | } 27 | 28 | protected override void Initialize() { 29 | base.Initialize(); 30 | 31 | _batch = new SpriteBatch(GraphicsDevice); 32 | 33 | _graphics.PreferredBackBufferWidth = 512; 34 | _graphics.PreferredBackBufferHeight = 512; 35 | _graphics.ApplyChanges(); 36 | } 37 | 38 | protected override void LoadContent() { 39 | base.LoadContent(); 40 | 41 | _graphicsBackend = new MonoGameGraphicsBackend(GraphicsDevice); 42 | _emulator = new Emulator(_graphicsBackend, new MonoGameAudioBackend(), new MonoGameInputBackend()); 43 | 44 | if (!_emulator.CartridgeLoader.Load("testcarts/ma_puzzle.p8")) { 45 | Exit(); 46 | } 47 | } 48 | 49 | protected override void Update(GameTime gameTime) { 50 | base.Update(gameTime); 51 | var dt = (float) gameTime.ElapsedGameTime.TotalSeconds; 52 | 53 | _deltaUpdate30 += dt; 54 | 55 | while (_deltaUpdate30 >= UpdateTime30) { 56 | _deltaUpdate30 -= UpdateTime30; 57 | _emulator.Update30(); 58 | } 59 | 60 | _deltaUpdate60 += dt; 61 | 62 | while (_deltaUpdate60 >= UpdateTime60) { 63 | _deltaUpdate60 -= UpdateTime60; 64 | _emulator.Update60(); 65 | } 66 | 67 | _counter.Update(dt); 68 | Window.Title = $"{_counter.AverageFramesPerSecond} fps {_emulator.Graphics.drawCalls} calls"; 69 | 70 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 71 | Keyboard.GetState().IsKeyDown(Keys.Escape)) { 72 | Exit(); 73 | } 74 | } 75 | 76 | protected override void Draw(GameTime gameTime) { 77 | base.Draw(gameTime); 78 | 79 | var dt = (float)gameTime.ElapsedGameTime.TotalSeconds; 80 | var u = _emulator.CartridgeLoader.HighFps ? UpdateTime60 : UpdateTime30; 81 | 82 | _deltaDraw += dt; 83 | 84 | while (_deltaDraw >= u) { 85 | _deltaDraw -= u; 86 | _emulator.Graphics.drawCalls = 0; 87 | _emulator.Draw(); 88 | } 89 | 90 | GraphicsDevice.Clear(Color.Black); 91 | 92 | _batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise); 93 | _batch.Draw(_graphicsBackend.Surface, new Rectangle(0, 0, 512, 512), Color.White); 94 | _batch.End(); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/math/MathUnit.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.lua; 2 | using System; 3 | 4 | namespace Pico8Emulator.unit.math { 5 | public class MathUnit : Unit { 6 | private Random _random = new Random(); 7 | 8 | public MathUnit(Emulator emulator) : base(emulator) { 9 | 10 | } 11 | 12 | public override void DefineApi(LuaInterpreter script) { 13 | script.AddFunction("rnd", (Func)Rnd); 14 | script.AddFunction("srand", (Action)Srand); 15 | 16 | script.AddFunction("flr", (Func)Flr); 17 | script.AddFunction("sgn", (Func)Sgn); 18 | script.AddFunction("max", (Func)Max); 19 | script.AddFunction("min", (Func)Min); 20 | script.AddFunction("mid", (Func)Mid); 21 | script.AddFunction("abs", (Func)Abs); 22 | script.AddFunction("sqrt", (Func)Sqrt); 23 | 24 | script.AddFunction("cos", (Func)Cos); 25 | script.AddFunction("sin", (Func)Sin); 26 | script.AddFunction("atan2", (Func)Atan2); 27 | 28 | script.AddFunction("band", (Func)Band); 29 | script.AddFunction("bor", (Func)Bor); 30 | script.AddFunction("bxor", (Func)Bxor); 31 | script.AddFunction("bnot", (Func)Bnot); 32 | script.AddFunction("shl", (Func)Shl); 33 | script.AddFunction("shr", (Func)Shr); 34 | } 35 | 36 | public double Rnd(double? x = null) { 37 | if (!x.HasValue) { 38 | x = 1; 39 | } 40 | 41 | return _random.NextDouble() * x.Value; 42 | } 43 | 44 | public void Srand(int x) { 45 | _random = new Random(x); 46 | } 47 | 48 | public double Flr(double x) { 49 | return Math.Floor(x); 50 | } 51 | 52 | public double Sgn(double x) { 53 | return x < 0 ? -1 : 1; 54 | } 55 | 56 | public double Max(double x, double y) { 57 | return Math.Max(x, y); 58 | } 59 | 60 | public double Min(double x, double y) { 61 | return Math.Min(x, y); 62 | } 63 | 64 | public double Mid(double x, double y, double z) { 65 | return Max(Min(Max(x, y), z), Min(x, y)); 66 | } 67 | 68 | public double Abs(double x) { 69 | return Math.Abs(x); 70 | } 71 | 72 | public double Sqrt(double x) { 73 | return Math.Sqrt(x); 74 | } 75 | 76 | public double Cos(double x) { 77 | return Math.Cos(2 * x * Math.PI); 78 | } 79 | 80 | public double Sin(double x) { 81 | return -Math.Sin(2 * x * Math.PI); 82 | } 83 | 84 | public double Atan2(double dx, double dy) { 85 | return 1 - Math.Atan2(dy, dx) / (2 * Math.PI); 86 | } 87 | 88 | public double Band(float x, float y) { 89 | return Util.FixedToFloat(Util.FloatToFixed(x) & Util.FloatToFixed(y)); 90 | } 91 | 92 | public double Bor(float x, float y) { 93 | return Util.FixedToFloat(Util.FloatToFixed(x) | Util.FloatToFixed(y)); 94 | } 95 | 96 | public double Bxor(float x, float y) { 97 | return Util.FixedToFloat(Util.FloatToFixed(x) ^ Util.FloatToFixed(y)); 98 | } 99 | 100 | public double Bnot(float x) { 101 | return Util.FixedToFloat(~Util.FloatToFixed(x)); 102 | } 103 | 104 | public double Shl(float x, int n) { 105 | return Util.FixedToFloat(Util.FloatToFixed(x) << n); 106 | } 107 | 108 | public double Shr(float x, int n) { 109 | return Util.FixedToFloat(Util.FloatToFixed(x) >> n); 110 | } 111 | 112 | public double Lshr(float x, int n) { 113 | return Util.FixedToFloat((int)((uint)Util.FloatToFixed(x)) >> n); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/audio/AudioUnit.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.lua; 2 | using Pico8Emulator.unit.mem; 3 | using System; 4 | 5 | namespace Pico8Emulator.unit.audio { 6 | public class AudioUnit : Unit { 7 | public const int SampleRate = 48000; 8 | public const int BufferSize = 2048; 9 | public const int ChannelCount = 4; 10 | 11 | public Sfx[] sfxChannels = new Sfx[ChannelCount]; 12 | public float[] externalAudioBuffer = new float[BufferSize]; 13 | public float[] audioBuffer = new float[BufferSize]; 14 | 15 | private MusicPlayer _musicPlayer; 16 | 17 | public AudioUnit(Emulator emulator) : base(emulator) { 18 | _musicPlayer = new MusicPlayer(Emulator); 19 | } 20 | 21 | public override void OnCartridgeLoad() { 22 | _musicPlayer.LoadMusic(); 23 | } 24 | 25 | public override void DefineApi(LuaInterpreter script) { 26 | base.DefineApi(script); 27 | 28 | script.AddFunction("music", (Action)Music); 29 | script.AddFunction("sfx", (Action)Sfx); 30 | } 31 | 32 | public override void Update() { 33 | base.Update(); 34 | Emulator.AudioBackend.Update(); 35 | } 36 | 37 | public float[] RequestBuffer() { 38 | ClearBuffer(); 39 | FillBuffer(); 40 | CompressBuffer(); 41 | 42 | Buffer.BlockCopy(audioBuffer, 0, externalAudioBuffer, 0, sizeof(float) * BufferSize); 43 | return externalAudioBuffer; 44 | } 45 | 46 | public void Sfx(int n, int? channel = -1, int? offset = 0, int? length = 32) { 47 | switch (n) { 48 | case -1: 49 | if (channel == -1) { 50 | StopAllChannelCount(); 51 | break; 52 | } 53 | 54 | if (channel < 0 || channel >= ChannelCount) 55 | break; 56 | 57 | sfxChannels[channel.Value] = null; 58 | break; 59 | case -2: 60 | if (channel.Value < 0 || channel.Value >= ChannelCount) 61 | break; 62 | 63 | if (sfxChannels[channel.Value] != null) { 64 | sfxChannels[channel.Value].loop = false; 65 | } 66 | 67 | break; 68 | default: 69 | // If sound is already playing, stop it. 70 | int? index = FindSoundOnAChannel(n); 71 | 72 | if (index != null) { 73 | sfxChannels[index.Value] = null; 74 | } 75 | 76 | if (channel == -1) { 77 | channel = FindAvailableChannel(); 78 | 79 | if (channel == null) 80 | break; 81 | } 82 | 83 | if (channel == -2) { 84 | break; 85 | } 86 | 87 | byte[] _sfxData = new byte[68]; 88 | Buffer.BlockCopy(Emulator.Memory.ram, RamAddress.Sfx + 68 * n, _sfxData, 0, 68); 89 | 90 | var osc = new Oscillator(SampleRate); 91 | sfxChannels[channel.Value] = new Sfx(_sfxData, n, ref audioBuffer, ref osc, SampleRate); 92 | sfxChannels[channel.Value].CurrentNote = offset.Value; 93 | sfxChannels[channel.Value].LastIndex = offset.Value + length.Value - 1; 94 | sfxChannels[channel.Value].Start(); 95 | break; 96 | } 97 | } 98 | 99 | public void Music(int n, int? fade_len = null, int? channel_mask = null) { 100 | _musicPlayer.Start(n); 101 | } 102 | 103 | public void FillBuffer() { 104 | for (int i = 0; i < ChannelCount; i += 1) { 105 | var s = sfxChannels[i]; 106 | 107 | if (s != null && !s.Update()) { 108 | sfxChannels[i] = null; 109 | } 110 | } 111 | 112 | _musicPlayer.Update(); 113 | } 114 | 115 | public void ClearBuffer() { 116 | for (int i = 0; i < BufferSize; i++) { 117 | audioBuffer[i] = 0; 118 | } 119 | } 120 | 121 | public void CompressBuffer() { 122 | for (int i = 0; i < BufferSize; i++) { 123 | audioBuffer[i] = (float)Math.Tanh(audioBuffer[i]); 124 | } 125 | } 126 | 127 | private int? FindAvailableChannel() { 128 | for (int i = 0; i < ChannelCount; i += 1) { 129 | if (sfxChannels[i] == null) { 130 | return i; 131 | } 132 | } 133 | 134 | return null; 135 | } 136 | 137 | private int? FindSoundOnAChannel(int n) { 138 | for (int i = 0; i < ChannelCount; i += 1) { 139 | var s = sfxChannels[i]; 140 | 141 | if (s != null && s.SfxIndex == n) { 142 | return i; 143 | } 144 | } 145 | 146 | return null; 147 | } 148 | 149 | private void StopChannel(int index) { 150 | sfxChannels[index] = null; 151 | } 152 | 153 | private void StopAllChannelCount() { 154 | for (int i = 0; i < ChannelCount; i += 1) { 155 | StopChannel(i); 156 | } 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/audio/MusicPlayer.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.unit.mem; 2 | using System; 3 | 4 | namespace Pico8Emulator.unit.audio { 5 | public class MusicPlayer { 6 | private PatternData[] _patternData; 7 | private Sfx[] _channels; 8 | private int _patternIndex; 9 | private Emulator _emulator; 10 | 11 | private Sfx _referenceSfx; 12 | 13 | private Oscillator _oscillator; 14 | 15 | public bool IsPlaying { get; private set; } 16 | 17 | public MusicPlayer(Emulator emulator) { 18 | _emulator = emulator; 19 | } 20 | 21 | public void LoadMusic() { 22 | _channels = new Sfx[4] { null, null, null, null }; 23 | IsPlaying = false; 24 | 25 | _oscillator = new Oscillator(AudioUnit.SampleRate); 26 | _patternData = new PatternData[64]; 27 | 28 | for (int i = 0; i < _patternData.Length; i += 1) { 29 | byte[] vals = { 30 | _emulator.Memory.ram[i * 4 + 0 + RamAddress.Song], 31 | _emulator.Memory.ram[i * 4 + 1 + RamAddress.Song], 32 | _emulator.Memory.ram[i * 4 + 2 + RamAddress.Song], 33 | _emulator.Memory.ram[i * 4 + 3 + RamAddress.Song] 34 | }; 35 | 36 | if ((vals[0] & 0x80) == 0x80) { 37 | _patternData[i].loopStart = true; 38 | } 39 | 40 | if ((vals[1] & 0x80) == 0x80) { 41 | _patternData[i].loopEnd = true; 42 | } 43 | 44 | if ((vals[2] & 0x80) == 0x80) { 45 | _patternData[i].shouldStop = true; 46 | } 47 | 48 | _patternData[i].channelCount = new ChannelData[4]; 49 | 50 | for (int j = 0; j < 4; j += 1) { 51 | _patternData[i].channelCount[j] = new ChannelData(); 52 | 53 | if ((vals[j] & 0b01000000) != 0) { 54 | _patternData[i].channelCount[j].isSilent = true; 55 | } 56 | 57 | _patternData[i].channelCount[j].sfxIndex = (byte)(vals[j] & 0b00111111); 58 | } 59 | } 60 | } 61 | 62 | public void Update() { 63 | if (!IsPlaying || _patternIndex > 63 || _patternIndex < 0) { 64 | return; 65 | } 66 | 67 | Process(); 68 | 69 | if (IsPatternDone()) { 70 | if (_patternData[_patternIndex].shouldStop) { 71 | Stop(); 72 | return; 73 | } 74 | 75 | if (_patternData[_patternIndex].loopEnd) { 76 | _patternIndex = FindClosestLoopStart(_patternIndex); 77 | } 78 | else { 79 | _patternIndex += 1; 80 | 81 | if (_patternIndex > 63) { 82 | Stop(); 83 | return; 84 | } 85 | } 86 | 87 | SetUpPattern(); 88 | Process(); 89 | } 90 | } 91 | 92 | private int FindClosestLoopStart(int index) { 93 | for (int i = index; i >= 0; i -= 1) { 94 | if (_patternData[i].loopStart) 95 | return i; 96 | } 97 | 98 | return 0; 99 | } 100 | 101 | private void Process() { 102 | for (int i = 0; i < 4; i += 1) { 103 | if (_channels[i] == null) 104 | continue; 105 | 106 | if (!_channels[i].Update()) { 107 | _channels[i] = null; 108 | continue; 109 | } 110 | } 111 | } 112 | 113 | private bool IsPatternDone() { 114 | if (_referenceSfx != null && !_referenceSfx.IsAlive) 115 | return true; 116 | 117 | return false; 118 | } 119 | 120 | private void SetUpPattern() { 121 | bool areAllLooping = true; 122 | Sfx longest = null; 123 | Sfx longestNoLoop = null; 124 | int audioBufferIndex = _referenceSfx?.AudioBufferIndex ?? 0; 125 | 126 | for (int i = 0; i < 4; i += 1) { 127 | if (_patternData[_patternIndex].channelCount[i].isSilent) { 128 | _channels[i] = null; 129 | continue; 130 | } 131 | 132 | byte[] _channelsData = new byte[68]; 133 | Buffer.BlockCopy(_emulator.Memory.ram, RamAddress.Sfx + 68 * _patternData[_patternIndex].channelCount[i].sfxIndex, _channelsData, 0, 68); 134 | 135 | _channels[i] = new Sfx(_channelsData, _patternData[_patternIndex].channelCount[i].sfxIndex, ref _emulator.Audio.audioBuffer, ref _oscillator, 136 | AudioUnit.SampleRate, audioBufferIndex); 137 | 138 | _channels[i].Start(); 139 | 140 | if (!_channels[i].HasLoop()) { 141 | areAllLooping = false; 142 | 143 | if (longestNoLoop == null || longestNoLoop.duration < _channels[i].duration) 144 | longestNoLoop = _channels[i]; 145 | } 146 | 147 | if (longest == null || longest.duration < _channels[i].duration) 148 | longest = _channels[i]; 149 | } 150 | 151 | _referenceSfx = areAllLooping ? longest : longestNoLoop; 152 | _referenceSfx.endLoop = _referenceSfx.startLoop; 153 | } 154 | 155 | public void Start(int n) { 156 | IsPlaying = true; 157 | _patternIndex = n; 158 | 159 | SetUpPattern(); 160 | } 161 | 162 | public void Stop() { 163 | IsPlaying = false; 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /MonoGamePico8/MonoGamePico8.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BA524303-7074-4C9E-8650-59E2C1293A56} 8 | Exe 9 | Properties 10 | MonoGamePico8 11 | MonoGamePico8 12 | v4.7.1 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\MonoGame.Framework.DesktopGL.3.7.0.1708\lib\net45\MonoGame.Framework.dll 37 | True 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | PreserveNewest 57 | 58 | 59 | PreserveNewest 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | PreserveNewest 66 | 67 | 68 | 69 | 70 | {ec0602b1-5a17-4cdc-bbb7-f2c5401ff899} 71 | Pico8Emulator 72 | 73 | 74 | 75 | 76 | 77 | 78 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /Pico8Emulator/Pico8Emulator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EC0602B1-5A17-4CDC-BBB7-F2C5401FF899} 8 | Library 9 | Properties 10 | Pico8Emulator 11 | Pico8Emulator 12 | v4.7.1 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll 37 | True 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ..\packages\System.Drawing.Common.4.6.0\lib\net461\System.Drawing.Common.dll 46 | True 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {3b26aa2e-0ba2-42dc-ad62-e6c8617f9e4d} 89 | Lens 90 | 91 | 92 | 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /Pico8Emulator/unit/audio/Oscillator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Pico8Emulator.unit.audio { 4 | public class Oscillator { 5 | /// 6 | /// Maps the waveform number in P8 to the actual waveform function. 7 | /// 8 | public Func[] waveFuncMap; 9 | 10 | /// 11 | /// How many samples per second is retrieved. 12 | /// 13 | public float sampleRate; 14 | 15 | /// 16 | /// Tracks how much time has passed after each audio sample request. 17 | /// 18 | private float _time; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// How many samples per second is retrieved. 24 | public Oscillator(float sampleRate) { 25 | waveFuncMap = new Func[] { 26 | Triangle, 27 | TiltedSaw, 28 | Sawtooth, 29 | Square, 30 | Pulse, 31 | Organ, // Organ 32 | Noise, // Noise 33 | Phaser // Phaser 34 | }; 35 | 36 | this.sampleRate = sampleRate; 37 | _tscale = Util.NoteToFrequency(63) / sampleRate; 38 | _time = 0.0f; 39 | } 40 | 41 | /// 42 | /// Sine wave. 43 | /// 44 | /// The frequency of the wave 45 | /// The sample value 46 | public float Sine(float frequency) { 47 | _time += frequency / sampleRate; 48 | return (float)Math.Sin(_time * 2 * Math.PI); 49 | } 50 | 51 | /// 52 | /// Square wave. 53 | /// 54 | /// The frequency of the wave 55 | /// The sample value 56 | public float Square(float frequency) { 57 | return Sine(frequency) >= 0 ? 1.0f : -1.0f; 58 | } 59 | 60 | /// 61 | /// Pulse Wave 62 | /// 63 | /// The frequency of the wave 64 | /// The sample value 65 | public float Pulse(float frequency) { 66 | _time += frequency / sampleRate; 67 | return ((_time) % 1 < 0.3125 ? 1 : -1) * 1.0f / 3.0f; 68 | } 69 | 70 | /// 71 | /// TiltedSaw Wave 72 | /// 73 | /// The frequency of the wave 74 | /// The sample value 75 | public float TiltedSaw(float frequency) { 76 | _time += frequency / sampleRate; 77 | var t = (_time) % 1; 78 | return (((t < 0.875) ? (t * 16 / 7) : ((1 - t) * 16)) - 1) * 0.7f; 79 | } 80 | 81 | /// 82 | /// Sawtooth Wave 83 | /// 84 | /// The frequency of the wave 85 | /// The sample value 86 | public float Sawtooth(float frequency) { 87 | _time += frequency / sampleRate; 88 | return (float)(2 * (_time - Math.Floor(_time + 0.5))); 89 | } 90 | 91 | /// 92 | /// Triangle Wave 93 | /// 94 | /// The frequency of the wave 95 | /// The sample value 96 | public float Triangle(float frequency) { 97 | _time += frequency / sampleRate; 98 | return (Math.Abs(((_time) % 1) * 2 - 1) * 2.0f - 1.0f) * 0.7f; 99 | } 100 | 101 | /// 102 | /// Organ Wave 103 | /// 104 | /// The frequency of the wave 105 | /// The sample value 106 | public float Organ(float frequency) { 107 | _time += frequency / sampleRate; 108 | var x = _time * 4; 109 | return (float)((Math.Abs((x % 2) - 1) - 0.5f + (Math.Abs(((x * 0.5) % 2) - 1) - 0.5f) / 2.0f - 0.1f) * 0.7f); 110 | } 111 | 112 | /// 113 | /// Phaser Wave 114 | /// 115 | /// The frequency of the wave 116 | /// The sample value 117 | public float Phaser(float frequency) { 118 | _time += frequency / sampleRate; 119 | var x = _time * 2; 120 | return (Math.Abs((x % 2) - 1) - 0.5f + (Math.Abs(((x * 127 / 128) % 2) - 1) - 0.5f) / 2) - 1.0f / 4.0f; 121 | } 122 | 123 | private float _lastx = 0; 124 | private float _sample = 0; 125 | private float _tscale; 126 | private Random _random = new Random(); 127 | 128 | /// 129 | /// White Noise Effect 130 | /// 131 | /// The frequency of the wave 132 | /// The sample value 133 | public float Noise(float frequency) { 134 | _time += frequency / sampleRate; 135 | float scale = (_time - _lastx) / _tscale; 136 | float lsample = _sample; 137 | _sample = (lsample + scale * ((float)_random.NextDouble() * 2 - 1)) / (1.0f + scale); 138 | _lastx = _time; 139 | return Math.Min(Math.Max((lsample + _sample) * 4.0f / 3.0f * (1.75f - scale), -1), 1) * 0.7f; 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/mem/MemoryUnit.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.lua; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Pico8Emulator.unit.mem { 7 | public class MemoryUnit : Unit { 8 | public const int Size = RamAddress.End; 9 | 10 | public readonly byte[] ram = new byte[Size]; 11 | public DrawState drawState; 12 | 13 | public MemoryUnit(Emulator emulator) : base(emulator) { 14 | drawState = new DrawState(this); 15 | } 16 | 17 | public override void DefineApi(LuaInterpreter script) { 18 | base.DefineApi(script); 19 | 20 | script.AddFunction("memset", (Func)Memset); 21 | script.AddFunction("memcpy", (Func)Memcpy); 22 | script.AddFunction("peek", (Func)Peek); 23 | script.AddFunction("poke", (Func)Poke); 24 | 25 | script.AddFunction("fget", (Func)Fget); 26 | script.AddFunction("fset", (Action)Fset); 27 | script.AddFunction("mget", (Func)Mget); 28 | script.AddFunction("mset", (Action)Mset); 29 | 30 | drawState.DefineApi(script); 31 | } 32 | 33 | public void LoadCartridgeData(byte[] cartridgeRom) { 34 | Buffer.BlockCopy(cartridgeRom, 0x0, ram, 0, 0x4300); 35 | } 36 | 37 | public object Memset(int destination, byte val, int len) { 38 | for (int i = 0; i < len; i++) { 39 | ram[destination + i] = val; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | public object Memcpy(int destination, int source, int len) { 46 | Buffer.BlockCopy(ram, source, ram, destination, len); 47 | 48 | return null; 49 | } 50 | 51 | public object Memcpy(int destination, int source, int len, byte[] src) { 52 | Buffer.BlockCopy(src, source, ram, destination, len); 53 | 54 | return null; 55 | } 56 | 57 | public byte Peek(int address) { 58 | if (address < 0 || address >= 0x8000) { 59 | return 0; 60 | } 61 | 62 | return ram[address]; 63 | } 64 | 65 | public object Poke(int address, byte val) { 66 | /* 67 | * FIXME: better error handling 68 | */ 69 | Trace.Assert(address >= 0 && address < 0x8000, "bad memory access"); 70 | ram[address] = val; 71 | 72 | return null; 73 | } 74 | 75 | public int Peek2(int addr) { 76 | if (addr < 0 || addr >= 0x8000 - 1) { 77 | return 0; 78 | } 79 | 80 | return ram[addr] | (ram[addr + 1] << 8); 81 | } 82 | 83 | public object Poke2(int address, int val) { 84 | /* 85 | * FIXME: better error handling 86 | */ 87 | Trace.Assert(address >= 0 && address < 0x8000, "bad memory access"); 88 | 89 | ram[address] = (byte)(val & 0xff); 90 | ram[address + 1] = (byte)((val >> 8) & 0xff); 91 | 92 | return null; 93 | } 94 | 95 | public double Peek4(int address) { 96 | if (address < 0 || address >= 0x8000 - 3) return 0; 97 | int right = ram[address] | (ram[address + 1] << 8); 98 | int left = ((ram[address + 2] << 16) | (ram[address + 3] << 24)); 99 | 100 | return Util.FixedToFloat(left + right); 101 | } 102 | 103 | public object Poke4(int address, double val) { 104 | /* 105 | * FIXME: better error handling 106 | */ 107 | Trace.Assert(address >= 0 && address < 0x8000, "bad memory access"); 108 | 109 | Int32 f = Util.FloatToFixed(val); 110 | 111 | ram[address] = (byte)(f & 0xff); 112 | ram[address + 1] = (byte)((f >> 8) & 0xff); 113 | ram[address + 2] = (byte)((f >> 16) & 0xff); 114 | ram[address + 3] = (byte)((f >> 24) & 0xff); 115 | 116 | return null; 117 | } 118 | 119 | public object Fget(int n, byte? f = null) { 120 | if (f.HasValue) { 121 | return (Peek(RamAddress.GfxProps + n) & (1 << f)) != 0; 122 | } 123 | 124 | return Peek(RamAddress.GfxProps + n); 125 | } 126 | 127 | public void Fset(int n, byte? f = null, bool? v = null) { 128 | if (!f.HasValue) { 129 | return; 130 | } 131 | 132 | if (v.HasValue) { 133 | if (v.Value) { 134 | Poke(RamAddress.GfxProps + n, (byte)(Peek(RamAddress.GfxProps + n) | (1 << f))); 135 | } 136 | else { 137 | Poke(RamAddress.GfxProps + n, (byte)(Peek(RamAddress.GfxProps + n) & ~(1 << f))); 138 | } 139 | } 140 | else { 141 | Poke(RamAddress.GfxProps + n, (byte)(Peek(RamAddress.GfxProps + n) | f)); 142 | } 143 | } 144 | 145 | public byte Mget(int x, int y) { 146 | int addr = (y < 32 ? RamAddress.Map : RamAddress.GfxMap); 147 | y = y & 0x1f; 148 | int index = ((y << 7) + x); 149 | 150 | if (index < 0 || index > 32 * 128 - 1) { 151 | return 0x0; 152 | } 153 | 154 | return ram[index + addr]; 155 | } 156 | 157 | public void Mset(int x, int y, byte v) { 158 | int addr = (y < 32 ? RamAddress.Map : RamAddress.GfxMap); 159 | y = y & 0x1f; 160 | int index = ((y << 7) + x); 161 | 162 | if (index < 0 || index > 32 * 128 - 1) { 163 | return; 164 | } 165 | 166 | ram[index + addr] = v; 167 | } 168 | 169 | public byte GetPixel(int x, int y, int offset = RamAddress.Screen) { 170 | int index = (y * 128 + x) / 2; 171 | 172 | if (index < 0 || index > 64 * 128 - 1) { 173 | return 0x10; 174 | } 175 | 176 | return Util.GetHalf(ram[index + offset], (x & 1) == 0); 177 | } 178 | 179 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 180 | public void WritePixel(int x, int y, byte color, int offset) { 181 | int index = (((y << 7) + x) >> 1) + offset; 182 | 183 | if (x < drawState.ClipLeft || y < drawState.ClipTop || x > drawState.ClipRight || y > drawState.ClipBottom) { 184 | return; 185 | } 186 | 187 | if ((x & 1) == 0) { 188 | ram[index] = (byte)((byte)(ram[index] & 0xf0) | (color & 0x0f)); 189 | } 190 | else { 191 | ram[index] = (byte)((byte)(ram[index] & 0x0f) | ((color & 0x0f) << 4)); 192 | } 193 | } 194 | 195 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 196 | public void WritePixel(int x, int y, byte color) { 197 | if (x < drawState.ClipLeft || y < drawState.ClipTop || x > drawState.ClipRight || y > drawState.ClipBottom) { 198 | return; 199 | } 200 | 201 | int index = (((y << 7) + x) >> 1) + 0x6000; 202 | 203 | if ((x & 1) == 0) { 204 | ram[index] = (byte)((byte)(ram[index] & 0xf0) | (color & 0x0f)); 205 | } 206 | else { 207 | ram[index] = (byte)((byte)(ram[index] & 0x0f) | ((color & 0x0f) << 4)); 208 | } 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/audio/Sfx.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Pico8Emulator.unit.audio { 4 | public class Sfx { 5 | public PicoNote[] notes; 6 | public float duration; 7 | public byte startLoop; 8 | public byte endLoop; 9 | public bool loop = true; 10 | private int _sampleRate; 11 | 12 | public bool IsAlive { get; private set; } 13 | public bool IsActive { get; private set; } 14 | public int SfxIndex { get; private set; } 15 | 16 | private float[] _audioBuffer; 17 | 18 | private int _currentNote = 0; 19 | 20 | public int CurrentNote { 21 | get { return _currentNote; } 22 | set { 23 | if (value < 0) _currentNote = 0; 24 | else if (value >= 32) _currentNote = 31; 25 | else _currentNote = value; 26 | } 27 | } 28 | 29 | private int _lastIndex = 31; 30 | 31 | public int LastIndex { 32 | get { return _lastIndex; } 33 | set { 34 | if (value < 0) _lastIndex = 0; 35 | else if (value >= 32) _lastIndex = 31; 36 | else _lastIndex = value; 37 | } 38 | } 39 | 40 | private Oscillator _oscillator; 41 | private Queue _notesToPlay; 42 | 43 | private float _fadeIn; 44 | 45 | public int AudioBufferIndex { get; private set; } 46 | 47 | public Sfx(byte[] _sfxData, int _sfxIndex, ref float[] audioBuffer, ref Oscillator oscillator, int sampleRate, 48 | int audioBufferIndex = 0) { 49 | notes = new PicoNote[32]; 50 | _audioBuffer = audioBuffer; 51 | 52 | duration = _sfxData[65] / 120.0f; 53 | startLoop = _sfxData[66]; 54 | endLoop = _sfxData[67]; 55 | 56 | _sampleRate = sampleRate; 57 | SfxIndex = _sfxIndex; 58 | 59 | _oscillator = oscillator; 60 | 61 | // Console.WriteLine($"header {_sfxData[64]} {_sfxData[65]} {_sfxData[66]} {_sfxData[67]}"); 62 | 63 | for (int i = 0; i < _sfxData.Length - 4; i += 2) { 64 | byte lo = _sfxData[i]; 65 | byte hi = _sfxData[i + 1]; 66 | 67 | notes[i / 2].pitch = (byte)(lo & 0b00111111); 68 | notes[i / 2].waveform = (byte)(((lo & 0b11000000) >> 6) | ((hi & 0b1) << 2)); 69 | notes[i / 2].volume = (byte)((hi & 0b00001110) >> 1); 70 | notes[i / 2].effect = (byte)((hi & 0b01110000) >> 4); 71 | notes[i / 2].isCustom = (byte)((hi & 0b10000000) >> 7) == 1; 72 | 73 | // Console.WriteLine($"{i} {notes[i / 2].pitch} {notes[i / 2].waveform} {notes[i / 2].volume} {notes[i / 2].effect} {notes[i / 2].isCustom}"); 74 | } 75 | 76 | oscillator = new Oscillator(sampleRate); 77 | _notesToPlay = new Queue(); 78 | 79 | this.AudioBufferIndex = audioBufferIndex; 80 | 81 | IsActive = true; 82 | 83 | _fadeIn = 0.05f / duration; 84 | } 85 | 86 | public bool Update() { 87 | if (!IsAlive) { 88 | return false; 89 | } 90 | 91 | while (AudioBufferIndex < AudioUnit.BufferSize) { 92 | // Queue next notes that need to be played. In case there are no more notes, stop everything. 93 | if (_notesToPlay.Count == 0) { 94 | QueueNextNotes(); 95 | 96 | if (_notesToPlay.Count == 0) { 97 | IsAlive = false; 98 | break; 99 | } 100 | } 101 | 102 | Note next = _notesToPlay.Peek(); 103 | AudioBufferIndex = next.Process(AudioBufferIndex, IsActive); 104 | 105 | if (AudioBufferIndex < AudioUnit.BufferSize) 106 | _notesToPlay.Dequeue(); 107 | } 108 | 109 | AudioBufferIndex = AudioBufferIndex == AudioUnit.BufferSize ? 0 : AudioBufferIndex; 110 | return IsAlive; 111 | } 112 | 113 | private void QueueNextNotes() { 114 | if (_currentNote > _lastIndex) { 115 | return; 116 | } 117 | 118 | var nextNote = notes[_currentNote]; 119 | 120 | switch (nextNote.effect) { 121 | case 0: 122 | ProcessNoteNoEffect(nextNote); 123 | _fadeIn = 0; 124 | break; 125 | case 1: 126 | ProcessNoteSlide(nextNote); 127 | _fadeIn = 0; 128 | break; 129 | case 2: 130 | ProcessNoteVibrato(nextNote); 131 | _fadeIn = 0; 132 | break; 133 | case 3: 134 | ProcessNoteDrop(nextNote); 135 | _fadeIn = 0; 136 | break; 137 | case 4: 138 | ProcessNoteFadeIn(nextNote); 139 | _fadeIn = 0; 140 | break; 141 | case 5: 142 | ProcessNoteFadeOut(nextNote); 143 | _fadeIn = 1.0f / duration; 144 | break; 145 | case 6: 146 | ProcessNoteArpeggioFast(nextNote); 147 | _fadeIn = 0; 148 | break; 149 | case 7: 150 | ProcessNoteArpeggioSlow(nextNote); 151 | _fadeIn = 0; 152 | break; 153 | default: 154 | _fadeIn = 0.5f / duration; 155 | break; 156 | } 157 | 158 | // If sfx has loop defined, process it. Otherwise keep incrementing note index. 159 | if (loop && startLoop < endLoop && _currentNote == endLoop - 1) { 160 | _currentNote = startLoop; 161 | } 162 | else { 163 | _currentNote += 1; 164 | } 165 | } 166 | 167 | public bool HasLoop() { 168 | return startLoop < endLoop; 169 | } 170 | 171 | private void ProcessNoteNoEffect(PicoNote note) { 172 | Note noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 173 | note.pitch, note.pitch, _fadeIn, 0); 174 | 175 | _notesToPlay.Enqueue(noteToPlay); 176 | } 177 | 178 | private void ProcessNoteSlide(PicoNote note) { 179 | int pitchFrom = _currentNote == 0 ? 32 : notes[_currentNote - 1].pitch; 180 | 181 | Note noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 182 | note.pitch, pitchFrom, _fadeIn, 0); 183 | 184 | _notesToPlay.Enqueue(noteToPlay); 185 | } 186 | 187 | private void ProcessNoteVibrato(PicoNote note) { 188 | Note noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 189 | note.pitch, note.pitch, 0, 0, true); 190 | 191 | _notesToPlay.Enqueue(noteToPlay); 192 | } 193 | 194 | private void ProcessNoteDrop(PicoNote note) { 195 | var noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 196 | 0, note.pitch, 0, 0); 197 | 198 | _notesToPlay.Enqueue(noteToPlay); 199 | } 200 | 201 | private void ProcessNoteFadeIn(PicoNote note) { 202 | var noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 203 | note.pitch, note.pitch, 95, 5); 204 | 205 | _notesToPlay.Enqueue(noteToPlay); 206 | } 207 | 208 | private void ProcessNoteFadeOut(PicoNote note) { 209 | var noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 210 | note.pitch, note.pitch, 0, 95); 211 | 212 | _notesToPlay.Enqueue(noteToPlay); 213 | } 214 | 215 | private void ProcessNoteArpeggioFast(PicoNote note) { 216 | var noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 217 | note.pitch, note.pitch, 0, 0); 218 | 219 | _notesToPlay.Enqueue(noteToPlay); 220 | } 221 | 222 | private void ProcessNoteArpeggioSlow(PicoNote note) { 223 | var noteToPlay = new Note(ref _audioBuffer, _sampleRate, ref _oscillator, duration, note.volume, note.waveform, 224 | note.pitch, note.pitch, 0, 0); 225 | 226 | _notesToPlay.Enqueue(noteToPlay); 227 | } 228 | 229 | public void Start() { 230 | IsAlive = true; 231 | } 232 | 233 | public void Stop() { 234 | IsAlive = false; 235 | } 236 | } 237 | } -------------------------------------------------------------------------------- /Pico8Emulator/lua/LuaPatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Pico8Emulator.lua { 6 | public static class LuaPatcher { 7 | private static string[] emojis = { 8 | "…", "░", "➡️", "⧗", "▤", "⬆️", "☉", 9 | "🅾️", "◆", "█", "★", "⬇️", "✽", "●", 10 | "♥", "웃", "⌂", "⬅️", "▥", "❎", "🐱", 11 | "ˇ", "▒", "♪", "😐", "∧" 12 | }; 13 | 14 | private static char[] printableEmojis = { 15 | /*(char) 144, (char) 132, (char) 145, (char) 147, (char) 152, 16 | (char) 148, (char) 136, (char) 142, (char) 143, (char) 128, (char) 146, 17 | (char) 131, (char) 133, (char) 134, (char) 135, 18 | (char) 137, (char) 138, (char) 139, (char) 153, (char) 151, 19 | (char) 130, (char) 149, (char) 129, (char) 141, (char) 140, (char) 150*/ 20 | 21 | 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 22 | 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 23 | 'Z', 'X', 'C', 'V', 'B', 'B', 'N', 'M' 24 | }; 25 | 26 | public static string PatchCode(string picoCode) { 27 | // "if a != b" => "if a ~= b" 28 | picoCode = Regex.Replace(picoCode, @"!=", "~="); 29 | // "//" => "--" 30 | picoCode = Regex.Replace(picoCode, @"//", "--"); 31 | 32 | // Comments are removed, because some edge cases with if() conversion happen, and it's easier just to remove comments 33 | // Removes all multiline comments 34 | picoCode = Regex.Replace(picoCode, @"\-\-\s*\[\[([^\]\]]*)\]\]", "", RegexOptions.Multiline); 35 | // Removes all single line comments 36 | picoCode = Regex.Replace(picoCode, @"\-\-.*", ""); 37 | 38 | // Replace all emojis (like heart) with text code 39 | for (var i = 0; i < emojis.Length; i++) { 40 | // Some emojis are 2+ chars 41 | picoCode = picoCode.Replace(emojis[i], $"_{printableEmojis[i]}"); 42 | // Just to make sure we catch all of them, or lua will not like this 43 | picoCode = picoCode.Replace($"{emojis[i][0]}", $"_{printableEmojis[i]}"); 44 | } 45 | 46 | // Matches and replaces binary style numbers like "0b1010.101" to hex format. 47 | picoCode = Regex.Replace(picoCode, @"0b([0-1]+)(?:\.{0,1})([0-1]*){0,1}", ReplaceBinaryNumber, RegexOptions.Multiline); 48 | // Matches if statements with conditions sorrounded by parenthesis, followed by anything but 49 | // nothing, only whitespaces or 'then' statement. Example: 50 | // "if (a ~= b) a=b" => "if (a ~= b) then a=b end" 51 | picoCode = Regex.Replace(picoCode, @"[iI][fF]\s*(\(.*)$", ReplaceIfShorthand, RegexOptions.Multiline); 52 | // Matches = type expressions, like "a += b". 53 | picoCode = Regex.Replace(picoCode, @"([a-zA-Z_](?:[a-zA-Z0-9_]|(?:\.\s*))*(?:\[.*\])?)\s*([+\-*\/%])=\s*(.*)$", ReplaceUnaryShorthand, RegexOptions.Multiline); 54 | 55 | return picoCode; 56 | } 57 | 58 | private static string ReplaceBinaryNumber(Match binaryMatch) { 59 | string integerPart = Convert.ToInt32(binaryMatch.Groups[1].ToString(), 2).ToString("X"); 60 | string fracPart = binaryMatch.Groups[2].Success ? binaryMatch.Groups[2].ToString() : "0"; 61 | 62 | return string.Format("0x{0}.{1}", integerPart, fracPart); 63 | } 64 | 65 | private static string ReplaceUnaryShorthand(Match unaryMatch) { 66 | // Replaces every possible "." with spaces after with only "." and then recursively calls 67 | // the same function looking for matches of the same unary shorthand. 68 | // This needs to be done before processing the shorthand because we might see another 69 | // shorthand in the expression area. For example, "a += b + foo(function(c) c += 10 end)", 70 | // where we see another shorthand inside the expression on the right. 71 | string fixedExp = Regex.Replace(Regex.Replace(unaryMatch.Groups[3].ToString(), @"\.\s+", "."), 72 | @"([a-zA-Z_](?:[a-zA-Z0-9_]|(?:\.\s*))*(?:\[.*\])?)\s*([+\-*\/%])=\s*(.*)$", 73 | ReplaceUnaryShorthand, 74 | RegexOptions.Multiline); 75 | 76 | 77 | var terms = Regex.Matches(fixedExp, @"(?:\-?[0-9.]+)|(?:\-?(?:0x)[0-9._A-Fa-f]+)|(?:\-?[a-zA-Z_\]\[](?:[a-zA-Z0-9_\[\]]|(?:\.\s*))*(?:\[[^\]]\])*)"); 78 | if (terms.Count <= 0) return unaryMatch.ToString(); 79 | 80 | int currentChar = 0; 81 | int currentTermIndex = 0; 82 | bool expectTerm = true; 83 | 84 | while (currentChar < fixedExp.Length) { 85 | if (Regex.IsMatch(fixedExp[currentChar].ToString(), @"\s")) { 86 | currentChar += 1; 87 | continue; 88 | } 89 | 90 | if (currentTermIndex >= terms.Count) { 91 | currentChar = fixedExp.Length; 92 | break; 93 | } 94 | 95 | if (terms[currentTermIndex].Index > currentChar) { 96 | if (currentChar < fixedExp.Length - 1) { 97 | var relationalOp = fixedExp.Substring(currentChar, 2); 98 | if (Regex.IsMatch(relationalOp, @"(?:\<\=)|(?:\>\=)|(?:\~\=)|(?:\=\=)")) { 99 | currentChar += 2; 100 | expectTerm = true; 101 | continue; 102 | } 103 | } 104 | 105 | if (Regex.IsMatch(fixedExp[currentChar].ToString(), @"[\-\+\=\/\*\%\<\>\~]")) { 106 | currentChar += 1; 107 | expectTerm = true; 108 | } 109 | else if (Regex.IsMatch(fixedExp[currentChar].ToString(), @"\(|\[|{")) { 110 | var st = new Stack(); 111 | st.Push(fixedExp[currentChar]); 112 | currentChar += 1; 113 | while (st.Count > 0) { 114 | if (currentChar >= fixedExp.Length) { 115 | break; 116 | } 117 | 118 | if (Regex.IsMatch(fixedExp[currentChar].ToString(), @"\)|\]|}")) { 119 | st.Pop(); 120 | } 121 | else if (Regex.IsMatch(fixedExp[currentChar].ToString(), @"\(|\[|{")) { 122 | st.Push(fixedExp[currentChar]); 123 | } 124 | 125 | currentChar += 1; 126 | } 127 | 128 | while (currentTermIndex < terms.Count && terms[currentTermIndex].Index < currentChar) { 129 | currentTermIndex += 1; 130 | } 131 | 132 | expectTerm = false; 133 | } 134 | } 135 | else { 136 | if (terms[currentTermIndex].Value.StartsWith("-")) expectTerm = true; 137 | 138 | if (!expectTerm) { 139 | break; 140 | } 141 | 142 | expectTerm = false; 143 | currentChar += terms[currentTermIndex].Length; 144 | currentTermIndex += 1; 145 | } 146 | } 147 | 148 | string expression = fixedExp.Substring(0, currentChar); 149 | string rest = fixedExp.Substring(currentChar); 150 | 151 | return string.Format("{0} = {0} {1} ({2}) {3}", unaryMatch.Groups[1], unaryMatch.Groups[2], expression, rest); 152 | } 153 | 154 | private static string ReplaceIfShorthand(Match ifMatch) { 155 | string ifLine = ifMatch.Groups[1].ToString(); 156 | 157 | if (ifLine.Contains("then")) { 158 | return $"if {ifLine}"; 159 | } 160 | 161 | // Remove the parenthesis from string. 162 | Stack st = new Stack(); 163 | st.Push(ifLine[0]); 164 | int currentChar = 1; 165 | while (st.Count > 0) { 166 | if (currentChar >= ifLine.Length) { 167 | break; 168 | } 169 | 170 | if (Regex.IsMatch(ifLine[currentChar].ToString(), @"\)|\]|}")) { 171 | st.Pop(); 172 | } 173 | else if (Regex.IsMatch(ifLine[currentChar].ToString(), @"\(|\[|{")) { 174 | st.Push(ifLine[currentChar]); 175 | } 176 | 177 | currentChar += 1; 178 | } 179 | 180 | string expression = ifLine.Substring(currentChar); 181 | string condition = ifLine.Substring(0, currentChar); 182 | 183 | if (!Regex.IsMatch(expression, @"(?:(?!(?:\s*$)|(?:\s*then)|(?:\s*and.*)|(?:\s*or.*)|(?:\s*not.*)))^.*$")) return ifMatch.Groups[0].ToString(); 184 | 185 | return string.Format("if {0} then {1} end", condition, expression); 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/mem/DrawState.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.lua; 2 | using Pico8Emulator.unit.graphics; 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Pico8Emulator.unit.mem { 7 | public class DrawState { 8 | private MemoryUnit _memory; 9 | private byte[] _ram; 10 | 11 | public DrawState(MemoryUnit mem) { 12 | _memory = mem; 13 | _ram = mem.ram; 14 | 15 | 16 | _ram[RamAddress.Palette0] = 0x10; 17 | _ram[RamAddress.Palette1] = 0x0; 18 | 19 | for (var i = 1; i < Palette.Size; i++) { 20 | _ram[RamAddress.Palette0 + i] = (byte)i; 21 | _ram[RamAddress.Palette1 + i] = (byte)i; 22 | } 23 | 24 | ClipLeft = 0; 25 | ClipTop = 0; 26 | ClipRight = 127; 27 | ClipBottom = 127; 28 | } 29 | 30 | public void DefineApi(LuaInterpreter script) { 31 | script.AddFunction("color", (Action)Color); 32 | script.AddFunction("cursor", (Action)Cursor); 33 | script.AddFunction("fillp", (Action)Fillp); 34 | script.AddFunction("camera", (Action)Camera); 35 | script.AddFunction("pal", (Action)Pal); 36 | script.AddFunction("palt", (Action)Palt); 37 | script.AddFunction("clip", (Action)Clip); 38 | 39 | Palt(); 40 | } 41 | 42 | public int CursorX { 43 | get => _ram[RamAddress.CursorX]; 44 | set => _ram[RamAddress.CursorX] = (byte)value; 45 | } 46 | 47 | public int CursorY { 48 | get => _ram[RamAddress.CursorY]; 49 | set => _ram[RamAddress.CursorY] = (byte)value; 50 | } 51 | 52 | private int _cameraX; 53 | public int CameraX { 54 | get => _cameraX; 55 | set { 56 | _ram[RamAddress.CameraX] = (byte)(value & 0xff); 57 | _ram[RamAddress.CameraX + 1] = (byte)(value >> 8); 58 | _cameraX = value; 59 | } 60 | } 61 | 62 | private int _cameraY; 63 | public int CameraY { 64 | get => _cameraY; 65 | set { 66 | _ram[RamAddress.CameraY] = (byte)(value & 0xff); 67 | _ram[RamAddress.CameraY + 1] = (byte)(value >> 8); 68 | _cameraY = value; 69 | } 70 | } 71 | 72 | public int LineX { 73 | get => ((sbyte)(_ram[RamAddress.LineX + 1]) << 8) | _ram[RamAddress.LineX]; 74 | set { 75 | _ram[RamAddress.LineX] = (byte)(value & 0xff); 76 | _ram[RamAddress.LineX + 1] = (byte)(value >> 8); 77 | } 78 | } 79 | 80 | public int LineY { 81 | get => ((sbyte)(_ram[RamAddress.LineY + 1]) << 8) | _ram[RamAddress.LineY]; 82 | set { 83 | _ram[RamAddress.LineY] = (byte)(value & 0xff); 84 | _ram[RamAddress.LineY + 1] = (byte)(value >> 8); 85 | } 86 | } 87 | 88 | /* 89 | * FIXME: completely broken 90 | */ 91 | public int ScreenX { 92 | get { 93 | // byte i = memory.Peek(Address.ScreenX); 94 | return 128; 95 | } 96 | } 97 | 98 | /* 99 | * FIXME: completely broken 100 | */ 101 | public int ScreenY { 102 | get { 103 | // byte i = Peek(Address.ScreenY); 104 | return 128; 105 | } 106 | } 107 | 108 | public int FillPattern { 109 | get => _fillPattern; 110 | set { 111 | _ram[RamAddress.FillPattern] = (byte)(value & 0xff); 112 | _ram[RamAddress.FillPattern + 1] = (byte)(value >> 8 & 0xff); 113 | _fillPattern = value; 114 | } 115 | } 116 | 117 | private int _fillPattern; 118 | 119 | public bool FillpTransparent { 120 | get => _ram[RamAddress.FillPattern + 2] != 0; 121 | set => _ram[RamAddress.FillPattern + 2] = (byte)(value ? 1 : 0); 122 | } 123 | 124 | private byte _clipLeft; 125 | public byte ClipLeft { 126 | get => _clipLeft; 127 | set { 128 | _ram[RamAddress.ClipLeft] = value; 129 | _clipLeft = (byte)(value & 0x7f); 130 | } 131 | } 132 | 133 | private byte _clipTop; 134 | public byte ClipTop { 135 | get => _clipTop; 136 | set { 137 | _ram[RamAddress.ClipTop] = value; 138 | _clipTop = (byte)(value & 0x7f); 139 | } 140 | } 141 | 142 | private byte _clipRight; 143 | public byte ClipRight { 144 | get => _clipRight; 145 | set { 146 | _ram[RamAddress.ClipRight] = value; 147 | _clipRight = (byte)(value & 0x7f); 148 | } 149 | } 150 | 151 | private byte _clipBottom; 152 | public byte ClipBottom { 153 | get => _clipBottom; 154 | set { 155 | _ram[RamAddress.ClipBottom] = value; 156 | _clipBottom = (byte)(value & 0x7f); 157 | } 158 | } 159 | 160 | public byte DrawColor { 161 | get => _ram[RamAddress.DrawColor]; 162 | set => _ram[RamAddress.DrawColor] = (byte)(value & 0xff); 163 | } 164 | 165 | public void Fillp(double? p = null) { 166 | if (!p.HasValue) { 167 | p = 0; 168 | } 169 | 170 | FillPattern = (int)p.Value; 171 | FillpTransparent = Math.Floor(p.Value) < p.Value; 172 | } 173 | 174 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 175 | public int GetFillPBit(int x, int y) { 176 | x &= 0b11; 177 | y &= 0b11; 178 | 179 | var i = (y << 2) + x; 180 | return (FillPattern & (1 << 15) >> i) >> (15 - i); 181 | } 182 | 183 | public void Cursor(int? x, int? y) { 184 | CursorX = x ?? 0; 185 | CursorY = y ?? 0; 186 | } 187 | 188 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 189 | public void Color(byte? col) { 190 | DrawColor = col ?? 6; 191 | } 192 | 193 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 194 | public byte GetDrawColor(int color) { 195 | return _ram[RamAddress.Palette0 + (color & 0x0f)]; 196 | } 197 | 198 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 199 | public int GetScreenColor(int color) { 200 | return _ram[RamAddress.Palette1 + (color & 0x0f)]; 201 | } 202 | 203 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 204 | public void SetTransparent(int col) { 205 | col &= 0x0f; 206 | 207 | _ram[RamAddress.Palette0 + col] &= 0x0f; 208 | _ram[RamAddress.Palette0 + col] |= 0x10; 209 | } 210 | 211 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 212 | public bool IsTransparent(int col) { 213 | return (_ram[RamAddress.Palette0 + col] & 0x10) != 0; 214 | } 215 | 216 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 217 | public void ResetTransparent(int col) { 218 | _ram[RamAddress.Palette0 + (col & 0x0f)] &= 0x0f; 219 | } 220 | 221 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 222 | public void SetDrawPalette(int c0, int c1) { 223 | var t = IsTransparent(c0); 224 | _ram[RamAddress.Palette0 + (c0 & 0x0f)] = (byte)(c1 & 0x0f); 225 | Palt(c0, t); 226 | } 227 | 228 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 229 | public void SetScreenPalette(int c0, int c1) { 230 | _ram[RamAddress.Palette1 + (c0 & 0x0f)] = (byte)(c1 & 0x0f); 231 | } 232 | 233 | public void Camera(int? x = null, int? y = null) { 234 | CameraX = x ?? 0; 235 | CameraY = y ?? 0; 236 | } 237 | 238 | public void Palt(int? col = null, bool t = false) { 239 | if (!col.HasValue && !t) { 240 | SetTransparent(0); 241 | 242 | for (byte i = 1; i < 16; i++) { 243 | ResetTransparent(i); 244 | } 245 | 246 | return; 247 | } 248 | 249 | if (t) { 250 | SetTransparent(col.Value); 251 | } 252 | else { 253 | ResetTransparent(col.Value); 254 | } 255 | } 256 | 257 | public void Pal(int? c0 = null, int? c1 = null, int p = 0) { 258 | if (!c0.HasValue || !c1.HasValue) { 259 | for (byte i = 0; i < 16; i++) { 260 | SetDrawPalette(i, i); 261 | SetScreenPalette(i, i); 262 | } 263 | 264 | Palt(); 265 | return; 266 | } 267 | 268 | if (p == 0) { 269 | SetDrawPalette(c0.Value, c1.Value); 270 | } 271 | else if (p == 1) { 272 | SetScreenPalette(c0.Value, c1.Value); 273 | } 274 | } 275 | 276 | public void Clip(int? x = null, int? y = null, int? w = null, int? h = null) { 277 | if (!x.HasValue || !y.HasValue || !w.HasValue || !h.HasValue) { 278 | ClipLeft = 0; 279 | ClipTop = 0; 280 | ClipRight = 127; 281 | ClipBottom = 127; 282 | 283 | return; 284 | } 285 | 286 | ClipLeft = (byte)x.Value; 287 | ClipTop = (byte)y.Value; 288 | ClipRight = (byte)(x.Value + w.Value); 289 | ClipBottom = (byte)(y.Value + h.Value); 290 | } 291 | } 292 | } -------------------------------------------------------------------------------- /Pico8Emulator/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! 10 | 11 | ### Usage 12 | 13 | Refer to the [Wiki Page](https://github.com/mmortatti/pico8-emulator/wiki/Pico8-Emulator-General-Usage) for information about usage. 14 | 15 | 16 | Flip = graphics.Flip; 17 | 18 | // Music 19 | Music = audio.Music; 20 | Sfx = audio.Sfx; 21 | 22 | Print = graphics.Print; 23 | 24 | // Math 25 | Max = Math.Max; 26 | Min = Math.Min; 27 | Mid = ((x, y, z) => Math.Max(Math.Min(Math.Max(x, y), z), Math.Min(x, y))); 28 | Floor = Math.Floor; 29 | Ceiling = Math.Ceiling; 30 | Cos = (x => Math.Cos(2 * x * Math.PI)); 31 | Sin = (x => -Math.Sin(2 * x * Math.PI)); 32 | Atan2 = ((dx, dy) => 1 - Math.Atan2(dy, dx) / (2 * Math.PI)); 33 | Sqrt = Math.Sqrt; 34 | Abs = Math.Abs; 35 | Band = (x, y) => Util.FixedToFloat(Util.FloatToFixed(x) & Util.FloatToFixed(y)); 36 | Bor = (x, y) => Util.FixedToFloat(Util.FloatToFixed(x) | Util.FloatToFixed(y)); 37 | Bxor = (x, y) => Util.FixedToFloat(Util.FloatToFixed(x) ^ Util.FloatToFixed(y)); 38 | Bnot = x => Util.FixedToFloat(~Util.FloatToFixed(x)); 39 | Shl = (x, n) => Util.FixedToFloat(Util.FloatToFixed(x) << n); 40 | Shr = (x, n) => Util.FixedToFloat(Util.FloatToFixed(x) >> n); 41 | Lshr = (x, n) => Util.FixedToFloat((int) ((uint) Util.FloatToFixed(x)) >> n); // Does Not Work I think 42 | 43 | // Graphics 44 | Line = graphics.Line; 45 | Rect = graphics.Rect; 46 | Rectfill = graphics.Rectfill; 47 | Circ = graphics.Circ; 48 | Circfill = graphics.CircFill; 49 | Pset = graphics.Pset; 50 | Pget = graphics.Pget; 51 | Sset = graphics.Sset; 52 | Sget = graphics.Sget; 53 | Palt = graphics.Palt; 54 | Pal = graphics.Pal; 55 | Clip = graphics.Clip; 56 | Spr = graphics.Spr; 57 | Sspr = graphics.Sspr; 58 | Map = graphics.Map; 59 | Mget = memory.Mget; 60 | Mset = memory.Mset; 61 | Fillp = memory.Fillp; 62 | 63 | // Memory related 64 | Cls = memory.Cls; 65 | Peek = memory.Peek; 66 | Poke = memory.Poke; 67 | Peek2 = memory.Peek2; 68 | Poke2 = memory.Poke2; 69 | Peek4 = memory.Peek4; 70 | Poke4 = memory.Poke4; 71 | Fget = memory.Fget; 72 | Fset = memory.Fset; 73 | Camera = memory.Camera; 74 | Memcpy = memory.Memcpy; 75 | Memset = memory.Memset; 76 | Color = memory.Color; 77 | 78 | // 79 | // Now, fill the lua API properly. 80 | // 81 | 82 | // Graphics 83 | interpreter.AddFunction("line", Line); 84 | interpreter.AddFunction("rect", Rect); 85 | interpreter.AddFunction("rectfill", Rectfill); 86 | interpreter.AddFunction("circ", Circ); 87 | interpreter.AddFunction("circfill", Circfill); 88 | interpreter.AddFunction("pset", Pset); 89 | interpreter.AddFunction("pget", Pget); 90 | interpreter.AddFunction("sset", Sset); 91 | interpreter.AddFunction("sget", Sget); 92 | interpreter.AddFunction("palt", Palt); 93 | interpreter.AddFunction("pal", Pal); 94 | interpreter.AddFunction("clip", Clip); 95 | interpreter.AddFunction("spr", Spr); 96 | interpreter.AddFunction("sspr", Sspr); 97 | interpreter.AddFunction("map", Map); 98 | interpreter.AddFunction("mget", Mget); 99 | interpreter.AddFunction("mset", Mset); 100 | interpreter.AddFunction("fillp", Fillp); 101 | 102 | // Memory related 103 | interpreter.AddFunction("cls", Cls); 104 | interpreter.AddFunction("peek", Peek); 105 | interpreter.AddFunction("poke", Poke); 106 | interpreter.AddFunction("peek2", Peek2); 107 | interpreter.AddFunction("poke2", Poke2); 108 | interpreter.AddFunction("peek4", Peek4); 109 | interpreter.AddFunction("poke4", Poke4); 110 | interpreter.AddFunction("fget", Fget); 111 | interpreter.AddFunction("fset", Fset); 112 | interpreter.AddFunction("camera", Camera); 113 | interpreter.AddFunction("memcpy", Memcpy); 114 | interpreter.AddFunction("memset", Memset); 115 | interpreter.AddFunction("reload", (Func) Reload); 116 | interpreter.AddFunction("cstore", (Func) Cstore); 117 | interpreter.AddFunction("cartdata", (Func) Cartdata); 118 | interpreter.AddFunction("dget", (Func) Dget); 119 | interpreter.AddFunction("dset", (Func) Dset); 120 | interpreter.AddFunction("color", Color); 121 | 122 | // Math 123 | interpreter.AddFunction("max", Max); 124 | interpreter.AddFunction("min", Min); 125 | interpreter.AddFunction("mid", Mid); 126 | interpreter.AddFunction("flr", Floor); 127 | interpreter.AddFunction("ceil", Ceiling); 128 | interpreter.AddFunction("cos", Cos); 129 | interpreter.AddFunction("sin", Sin); 130 | interpreter.AddFunction("atan2", Atan2); 131 | interpreter.AddFunction("sqrt", Sqrt); 132 | interpreter.AddFunction("abs", Abs); 133 | interpreter.AddFunction("rnd", (Func) Rnd); 134 | interpreter.AddFunction("srand", (Func) Srand); 135 | interpreter.AddFunction("band", Band); 136 | interpreter.AddFunction("bor", Bor); 137 | interpreter.AddFunction("bxor", Bxor); 138 | interpreter.AddFunction("bnot", Bnot); 139 | interpreter.AddFunction("shl", Shl); 140 | interpreter.AddFunction("shr", Shr); 141 | interpreter.AddFunction("lshr", Lshr); // Does Not Work I think 142 | 143 | // Controls 144 | interpreter.AddFunction("btn", (Func) Btn); 145 | interpreter.AddFunction("btnp", (Func) Btnp); 146 | 147 | interpreter.AddFunction("flip", Flip); 148 | 149 | // Music 150 | interpreter.AddFunction("music", Music); 151 | interpreter.AddFunction("sfx", Sfx); 152 | 153 | // Misc 154 | interpreter.AddFunction("time", (Func) Time); 155 | 156 | interpreter.AddFunction("print", Print); 157 | interpreter.AddFunction("printh", (Func) Printh); 158 | 159 | interpreter.AddFunction("menuitem", (Func) Menuitem); 160 | interpreter.AddFunction("import", (Func) Import); 161 | interpreter.AddFunction("export", (Func) Export); 162 | 163 | interpreter.RunScript(@" 164 | function all(collection) 165 | if (collection == nil) then return function() end end 166 | local index = 0 167 | local count = #collection 168 | return function () 169 | index = index + 1 170 | if index <= count 171 | then 172 | return collection[index] 173 | end 174 | end 175 | end 176 | 177 | function tostr(x) 178 | if type(x) == ""number"" then return tostring(math.floor(x*10000)/10000) end 179 | return tostring(x) 180 | end 181 | 182 | function tonum(x) 183 | return tonumber(x) 184 | end 185 | 186 | function add(t,v) 187 | if t == nil then return end 188 | table.insert(t,v) 189 | end 190 | 191 | function del(t,v) 192 | if t == nil then return end 193 | for i = 0,#t do 194 | if t[i] == v then 195 | table.remove(t,i) 196 | return 197 | end 198 | end 199 | end 200 | 201 | function foreach(t,f) 202 | for e in all(t) do 203 | f(e) 204 | end 205 | end 206 | 207 | function string:split(sep) 208 | local sep, fields = sep or "":"", {} 209 | local pattern = string.format(""([^%s]+)"", sep) 210 | self: gsub(pattern, function(c) fields[#fields+1] = c end) 211 | return fields 212 | end 213 | 214 | function b(s) 215 | local o = 0 216 | local n = s:split('.') 217 | for d in n[1]:gmatch('[01]') do 218 | o = o*2 + tonumber(d) 219 | end 220 | if n[2] then 221 | div = 2 222 | for d in n[2]:gmatch('[01]') do 223 | o = o + tonumber(d) / div 224 | div = div * 2 225 | end 226 | end 227 | 228 | return o 229 | end 230 | 231 | t = type 232 | function type(f) 233 | if not f then 234 | return 'nil' 235 | end 236 | return t(f) 237 | end 238 | 239 | cocreate = coroutine.create 240 | coresume = coroutine.resume 241 | costatus = coroutine.status 242 | yield = coroutine.yield 243 | sub = string.sub 244 | "); -------------------------------------------------------------------------------- /Pico8Emulator/unit/graphics/GraphicsUnit.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.lua; 2 | using Pico8Emulator.unit.mem; 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Pico8Emulator.unit.graphics { 7 | public class GraphicsUnit : Unit { 8 | public const int ScreenSize = 128 * 128; 9 | 10 | public int drawCalls; 11 | 12 | public GraphicsUnit(Emulator emulator) : base(emulator) { 13 | emulator.GraphicsBackend.CreateSurface(); 14 | } 15 | 16 | public override void Init() { 17 | base.Init(); 18 | 19 | Emulator.Memory.drawState.DrawColor = 6; 20 | 21 | Emulator.Memory.ram[RamAddress.Palette0] = 0x10; 22 | Emulator.Memory.ram[RamAddress.Palette1] = 0x0; 23 | 24 | for (int i = 1; i < 16; ++i) { 25 | Emulator.Memory.ram[RamAddress.Palette0 + i] = (byte)i; 26 | Emulator.Memory.ram[RamAddress.Palette1 + i] = (byte)i; 27 | } 28 | 29 | Emulator.Memory.ram[RamAddress.ClipLeft] = 0; 30 | Emulator.Memory.ram[RamAddress.ClipTop] = 0; 31 | Emulator.Memory.ram[RamAddress.ClipRight] = 127; 32 | Emulator.Memory.ram[RamAddress.ClipBottom] = 127; 33 | } 34 | 35 | public override void DefineApi(LuaInterpreter script) { 36 | base.DefineApi(script); 37 | 38 | script.AddFunction("pset", (Action)Pset); 39 | script.AddFunction("pget", (Func)Pget); 40 | script.AddFunction("flip", (Action)Flip); 41 | script.AddFunction("cls", (Action)Cls); 42 | 43 | script.AddFunction("spr", (Action)Spr); 44 | script.AddFunction("sspr", (Action)Sspr); 45 | 46 | script.AddFunction("print", (Action)Print); 47 | script.AddFunction("map", (Action)Map); 48 | // Old name of map 49 | script.AddFunction("mapdraw", (Action)Map); 50 | 51 | script.AddFunction("line", (Action)Line); 52 | script.AddFunction("rect", (Action)Rect); 53 | script.AddFunction("rectfill", (Action)Rectfill); 54 | script.AddFunction("circ", (Action)Circ); 55 | script.AddFunction("circfill", (Action)Circfill); 56 | 57 | script.AddFunction("sset", (Action)Sset); 58 | script.AddFunction("sget", (Func)Sget); 59 | } 60 | 61 | public void Cls(byte? color) { 62 | var c = 0; 63 | 64 | if (color.HasValue) { 65 | var v = color.Value & 0x0f; 66 | c = v | (v << 4); 67 | } 68 | 69 | for (var i = 0; i < 0x2000; i++) { 70 | Emulator.Memory.ram[RamAddress.Screen + i] = (byte)c; 71 | } 72 | } 73 | 74 | private static string InvertCasing(string s) { 75 | char[] c = s.ToCharArray(); 76 | char[] cUpper = s.ToUpper().ToCharArray(); 77 | char[] cLower = s.ToLower().ToCharArray(); 78 | 79 | for (int i = 0; i < c.Length; i++) { 80 | if (c[i] == cUpper[i]) { 81 | c[i] = cLower[i]; 82 | } else { 83 | c[i] = cUpper[i]; 84 | } 85 | } 86 | 87 | return new string(c); 88 | } 89 | 90 | public void Print(object s, int? x = null, int? y = null, byte? col = null) { 91 | if (x.HasValue) { 92 | Emulator.Memory.drawState.CursorX = x.Value; 93 | } 94 | else { 95 | x = Emulator.Memory.drawState.CursorX; 96 | } 97 | 98 | if (y.HasValue) { 99 | Emulator.Memory.drawState.CursorY = y.Value; 100 | } 101 | else { 102 | y = Emulator.Memory.drawState.CursorY; 103 | Emulator.Memory.drawState.CursorY += 6; 104 | } 105 | 106 | x -= Emulator.Memory.drawState.CameraX; 107 | y -= Emulator.Memory.drawState.CameraY; 108 | 109 | if (col.HasValue) { 110 | Emulator.Memory.drawState.DrawColor = col.Value; 111 | } 112 | 113 | var c = Emulator.Memory.drawState.DrawColor; 114 | var xOrig = x.Value; 115 | var prtStr = InvertCasing(s.ToString()); 116 | 117 | foreach (var l in prtStr) { 118 | if (l == '\n') { 119 | y += 6; 120 | x = xOrig; 121 | continue; 122 | } 123 | 124 | if (Font.dictionary.ContainsKey(l)) { 125 | byte[,] digit = Font.dictionary[l]; 126 | 127 | for (int i = 0; i < digit.GetLength(0); i += 1) { 128 | for (int j = 0; j < digit.GetLength(1); j += 1) { 129 | if (digit[i, j] == 1) { 130 | DrawPixel(x.Value + j, y.Value + i, c); 131 | } 132 | } 133 | } 134 | 135 | x += digit.GetLength(1) + 1; 136 | } 137 | } 138 | } 139 | 140 | public void Map(int? cellX, int? cellY, int? sx, int? sy, int? cellW, int? cellH, byte? layer = null) { 141 | var x = cellX ?? 0; 142 | var y = cellY ?? 0; 143 | var px = sx ?? 0; 144 | var py = sy ?? 0; 145 | var tw = cellW ?? 16; 146 | var th = cellH ?? 16; 147 | 148 | for (var h = 0; h < th; h++) { 149 | for (var w = 0; w < tw; w++) { 150 | var addr = (y + h) < 32 ? RamAddress.Map : RamAddress.GfxMap; 151 | var spr = Emulator.Memory.Peek(addr + (((y + h) & 0x1f) << 7) + x + w); 152 | 153 | // Spr index 0 is reserved for empty tiles 154 | if (spr == 0) { 155 | continue; 156 | } 157 | 158 | // If layer has not been specified, draw regardless 159 | if (!layer.HasValue || layer.Value == 0 || ((byte)Emulator.Memory.Fget(spr) & layer.Value) != 0) { 160 | Spr(spr, px + 8 * w, py + 8 * h, 1, 1); 161 | } 162 | } 163 | } 164 | } 165 | 166 | public void Flip() { 167 | Emulator.GraphicsBackend.Flip(); 168 | } 169 | 170 | public void Spr(int n, int? xx = null, int? yy = null, int? w = null, int? h = null, bool flipX = false, bool flipY = false) { 171 | if (n < 0 || n > 255) { 172 | return; 173 | } 174 | 175 | var x = xx ?? 0; 176 | var y = yy ?? 0; 177 | 178 | x -= Emulator.Memory.drawState.CameraX; 179 | y -= Emulator.Memory.drawState.CameraY; 180 | 181 | var sprX = (n & 0x0f) << 3; 182 | var sprY = (n >> 4) << 3; 183 | var width = 1; 184 | var height = 1; 185 | 186 | if (w.HasValue) { 187 | width = w.Value; 188 | } 189 | 190 | if (h.HasValue) { 191 | height = h.Value; 192 | } 193 | 194 | for (var i = 0; i < 8 * width; i++) { 195 | for (var j = 0; j < 8 * height; j++) { 196 | Spset(x + (flipX ? 8 * width - i : i), y + (flipY ? 8 * height - j : j), Sget(i + sprX, j + sprY)); 197 | } 198 | } 199 | 200 | drawCalls++; 201 | } 202 | 203 | public void Sspr(int sx, int sy, int sw, int sh, int dx, int dy, int? dw = null, int? dh = null, 204 | bool flipX = false, bool flipY = false) { 205 | dx -= Emulator.Memory.drawState.CameraX; 206 | dy -= Emulator.Memory.drawState.CameraY; 207 | 208 | if (!dw.HasValue) { 209 | dw = sw; 210 | } 211 | 212 | if (!dh.HasValue) { 213 | dh = sh; 214 | } 215 | 216 | float ratioX = sw / (float)dw.Value; 217 | float ratioY = sh / (float)dh.Value; 218 | float x = sx; 219 | float screenX = dx; 220 | float y; 221 | float screenY; 222 | 223 | while (x < sx + sw && screenX < dx + dw) { 224 | y = sy; 225 | screenY = dy; 226 | 227 | while (y < sy + sh && screenY < dy + dh) { 228 | Spset((flipX ? dx + dw.Value - ((int)screenX - dx) : (int)screenX), 229 | (flipY ? dy + dh.Value - ((int)screenY - dy) : (int)screenY), Sget((int)x, (int)y)); 230 | 231 | y += ratioY; 232 | screenY += 1; 233 | } 234 | 235 | x += ratioX; 236 | screenX += 1; 237 | } 238 | } 239 | 240 | public byte Sget(int x, int y) { 241 | return Emulator.Memory.GetPixel(x, y, RamAddress.Gfx); 242 | } 243 | 244 | public void Sset(int x, int y, byte? col = null) { 245 | if (col.HasValue) { 246 | Emulator.Memory.drawState.DrawColor = col.Value; 247 | } 248 | 249 | Emulator.Memory.WritePixel(x, y, Emulator.Memory.drawState.DrawColor, RamAddress.Gfx); 250 | } 251 | 252 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 253 | public void Pset(int x, int y, byte? col = null) { 254 | x -= Emulator.Memory.drawState.CameraX; 255 | y -= Emulator.Memory.drawState.CameraY; 256 | 257 | if (!col.HasValue) { 258 | col = Emulator.Memory.drawState.DrawColor; 259 | } 260 | 261 | DrawPixel(x, y, col.Value); 262 | 263 | Emulator.Memory.drawState.DrawColor = (byte)(col.Value & 0x0f); 264 | } 265 | 266 | /// 267 | /// _A special kind of Pset only used in the Spr and Sspr functions. 268 | /// It removes Fillp functionality and default color (DrawColor variable) update, 269 | /// since that should only work for functions like circ() and rect(). 270 | /// 271 | /// X screen position. 272 | /// Y screen position. 273 | /// Color to draw pixel. 274 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 275 | private void Spset(int x, int y, byte col) { 276 | // If the pixel is transparent, don't draw anything. 277 | if (!Emulator.Memory.drawState.IsTransparent(col)) { 278 | Emulator.Memory.WritePixel(x, y, Emulator.Memory.drawState.GetDrawColor(col & 0x0f)); 279 | } 280 | } 281 | 282 | public byte Pget(int x, int y) { 283 | return Emulator.Memory.GetPixel(x, y); 284 | } 285 | 286 | public void Rect(int x0, int y0, int x1, int y1, byte? col = null) { 287 | Line(x0, y0, x1, y0, col); 288 | Line(x0, y0, x0, y1, col); 289 | Line(x1, y1, x1, y0, col); 290 | Line(x1, y1, x0, y1, col); 291 | } 292 | 293 | public void Rectfill(int x0, int y0, int x1, int y1, byte? col = null) { 294 | if (y0 > y1) { 295 | Util.Swap(ref y0, ref y1); 296 | } 297 | 298 | for (var y = y0; y <= y1; y++) { 299 | Line(x0, y, x1, y, col); 300 | } 301 | } 302 | 303 | public void Line(int x0, int y0, int? x1 = null, int? y1 = null, byte? col = null) { 304 | if (x1.HasValue) { 305 | Emulator.Memory.drawState.LineX = x1.Value; 306 | } 307 | 308 | if (y1.HasValue) { 309 | Emulator.Memory.drawState.LineY = y1.Value; 310 | } 311 | 312 | if (col.HasValue) { 313 | Emulator.Memory.drawState.DrawColor = col.Value; 314 | } 315 | 316 | var x0_screen = x0 - Emulator.Memory.drawState.CameraX; 317 | var y0_screen = y0 - Emulator.Memory.drawState.CameraY; 318 | var x1_screen = Emulator.Memory.drawState.LineX - Emulator.Memory.drawState.CameraX; 319 | var y1_screen = Emulator.Memory.drawState.LineY - Emulator.Memory.drawState.CameraY; 320 | 321 | DrawLine(x0_screen, y0_screen, x1_screen, y1_screen, Emulator.Memory.drawState.DrawColor); 322 | } 323 | 324 | public void Circ(int x, int y, double? r, byte? col = null) { 325 | if (col.HasValue) { 326 | Emulator.Memory.drawState.DrawColor = col.Value; 327 | } 328 | 329 | x -= Emulator.Memory.drawState.CameraX; 330 | y -= Emulator.Memory.drawState.CameraY; 331 | 332 | DrawCircle(x, y, (int)Math.Ceiling(r ?? 1), false); 333 | } 334 | 335 | public void Circfill(int x, int y, double? r, byte? col = null) { 336 | if (col.HasValue) { 337 | Emulator.Memory.drawState.DrawColor = col.Value; 338 | } 339 | 340 | x -= Emulator.Memory.drawState.CameraX; 341 | y -= Emulator.Memory.drawState.CameraY; 342 | 343 | DrawCircle(x, y, (int)(r ?? 1), true); 344 | } 345 | 346 | private void Plot4(int x, int y, int offX, int offY, bool fill) { 347 | var c = Emulator.Memory.drawState.DrawColor; 348 | if (fill) { 349 | DrawLine((x - offX), (y + offY), (x + offX), (y + offY), c); 350 | 351 | if (offY != 0) { 352 | DrawLine((x - offX), (y - offY), (x + offX), (y - offY), c); 353 | } 354 | } 355 | else { 356 | DrawPixel((x - offX), (y + offY), c); 357 | DrawPixel((x + offX), (y + offY), c); 358 | 359 | if (offY != 0) { 360 | DrawPixel((x - offX), (y - offY), c); 361 | DrawPixel((x + offX), (y - offY), c); 362 | } 363 | } 364 | } 365 | 366 | // 367 | // Pure draw functions. 368 | // 369 | 370 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 371 | private void DrawCircle(int posX, int posY, int r, bool fill) { 372 | var x = r; 373 | var y = 0; 374 | double err = 1 - r; 375 | 376 | while (y <= x) { 377 | Plot4(posX, posY, x, y, fill); 378 | 379 | if (err < 0) { 380 | err = err + 2 * y + 3; 381 | } 382 | else { 383 | if (x != y) { 384 | Plot4(posX, posY, y, x, fill); 385 | } 386 | 387 | x = x - 1; 388 | err = err + 2 * (y - x) + 3; 389 | } 390 | 391 | y = y + 1; 392 | } 393 | } 394 | 395 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 396 | private void DrawLine(int x0, int y0, int x1, int y1, byte col) { 397 | var steep = false; 398 | 399 | if (Math.Abs(x1 - x0) < Math.Abs(y1 - y0)) { 400 | Util.Swap(ref x0, ref y0); 401 | Util.Swap(ref x1, ref y1); 402 | steep = true; 403 | } 404 | 405 | if (x0 > x1) { 406 | Util.Swap(ref x0, ref x1); 407 | Util.Swap(ref y0, ref y1); 408 | } 409 | 410 | var dx = x1 - x0; 411 | var dy = y1 - y0; 412 | var d_err = 2 * Math.Abs(dy); 413 | var err = 0; 414 | var y = y0; 415 | 416 | for (var x = x0; x <= x1; x++) { 417 | if (steep) { 418 | DrawPixel(y, x, col); 419 | } 420 | else { 421 | DrawPixel(x, y, col); 422 | } 423 | 424 | err += d_err; 425 | 426 | if (err > dx) { 427 | y += y1 > y0 ? 1 : -1; 428 | err -= dx * 2; 429 | } 430 | } 431 | } 432 | 433 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 434 | private void DrawPixel(int x, int y, byte c) { 435 | if (Emulator.Memory.drawState.GetFillPBit(x, y) == 0) { 436 | // Do not consider transparency bit for this operation. 437 | Emulator.Memory.WritePixel(x, y, (byte)(Emulator.Memory.drawState.GetDrawColor(c & 0x0f) & 0x0f)); 438 | } 439 | else if (!Emulator.Memory.drawState.FillpTransparent) { 440 | // Do not consider transparency bit for this operation. 441 | Emulator.Memory.WritePixel(x, y, (byte)(Emulator.Memory.drawState.GetDrawColor(c >> 4) & 0x0f)); 442 | } 443 | } 444 | } 445 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/cart/CartridgeUnit.cs: -------------------------------------------------------------------------------- 1 | using Pico8Emulator.lua; 2 | using Pico8Emulator.unit.graphics; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.Drawing.Imaging; 8 | using System.Globalization; 9 | using System.IO; 10 | using System.Text; 11 | using System.Text.RegularExpressions; 12 | 13 | namespace Pico8Emulator.unit.cart { 14 | public class CartridgeUnit : Unit { 15 | public Cartridge loaded; 16 | public bool HighFps; 17 | 18 | private DateTime _startTime; 19 | 20 | public CartridgeUnit(Emulator emulator) : base(emulator) { 21 | } 22 | 23 | public override void Update() { 24 | base.Update(); 25 | loaded?.interpreter.CallIfDefined("_update60"); 26 | } 27 | 28 | public void Update30() { 29 | base.Update(); 30 | loaded?.interpreter.CallIfDefined("_update"); 31 | } 32 | 33 | public void Draw() { 34 | if (loaded == null) { 35 | Log.Info("No cart loaded, nothing to draw!"); 36 | 37 | return; 38 | } 39 | 40 | if (loaded.interpreter.CallIfDefined("_draw")) { 41 | Emulator.Graphics.Flip(); 42 | } 43 | } 44 | 45 | public override void DefineApi(LuaInterpreter script) { 46 | base.DefineApi(script); 47 | 48 | script.AddFunction("reload", (Action)Reload); 49 | script.AddFunction("cstore", (Action)Cstore); 50 | script.AddFunction("cartdata", (Action)Cartdata); 51 | script.AddFunction("dget", (Func)Dget); 52 | script.AddFunction("dset", (Action)Dset); 53 | script.AddFunction("import", (Action)Import); 54 | script.AddFunction("export", (Action)Export); 55 | script.AddFunction("run", (Action)Run); 56 | 57 | script.AddFunction("time", (Func)Time); 58 | script.AddFunction("t", (Func)Time); 59 | } 60 | 61 | public bool Load(string name) { 62 | Log.Info($"Loading cart {name}"); 63 | 64 | var possibleNames = new[] { 65 | name, 66 | $"{name}.p8.png", 67 | $"{name}.png", 68 | $"{name}.p8" 69 | }; 70 | 71 | /* 72 | * TODO: this is super basic file searching, 73 | * should probably introduce filesystem unit or smth, 74 | * so that the user could set the search/root path 75 | */ 76 | foreach (var possibleName in possibleNames) { 77 | var path = Path.GetFullPath(possibleName); 78 | 79 | if (File.Exists(possibleName)) { 80 | Log.Info($"Found cart {possibleName}"); 81 | 82 | return ReadCart(possibleName, possibleName.EndsWith(".png")); 83 | } 84 | } 85 | 86 | /* 87 | * TODO: report the issue, that the cart was not found 88 | */ 89 | Log.Info($"Did not find the cart {name}"); 90 | 91 | return false; 92 | } 93 | 94 | private bool ReadCart(string name, bool image) { 95 | if (image) { 96 | /* 97 | * TODO: implement 98 | */ 99 | 100 | return false; 101 | } 102 | else { 103 | if (!LoadTextCart(name)) { 104 | return false; 105 | } 106 | } 107 | 108 | Run(); 109 | 110 | return true; 111 | } 112 | 113 | private bool LoadTextCart(string path) { 114 | var streamReader = new StreamReader(path); 115 | 116 | var stateMap = new Dictionary { 117 | {"__lua__", 0}, 118 | {"__gfx__", 1}, 119 | {"__gff__", 2}, 120 | {"__map__", 3}, 121 | {"__sfx__", 4}, 122 | {"__music__", 5}, 123 | {"__label__", 6} 124 | }; 125 | 126 | loaded = new Cartridge(); 127 | loaded.path = path; 128 | 129 | var state = -1; 130 | var index = 0; 131 | var codeBuilder = new StringBuilder(); 132 | string line; 133 | 134 | while (!streamReader.EndOfStream) { 135 | line = streamReader.ReadLine(); 136 | 137 | if (line == null) { 138 | /* 139 | * FIXME: report error?? 140 | */ 141 | break; 142 | } 143 | 144 | if (stateMap.ContainsKey(line)) { 145 | state = stateMap[line]; 146 | index = 0; 147 | 148 | continue; 149 | } 150 | 151 | if (state == -1) { 152 | if (Regex.IsMatch(line, @"[vV]ersion\ *")) { 153 | loaded.rom[RomAddress.Meta] = byte.Parse(Regex.Replace(line, @"[vV]ersion\ *", ""), NumberStyles.Integer); 154 | } 155 | } 156 | else if (state == stateMap["__lua__"]) { 157 | codeBuilder.AppendLine(line); 158 | } 159 | else if (state == stateMap["__gfx__"]) { 160 | foreach (var c in line) { 161 | var val = byte.Parse(c.ToString(), NumberStyles.HexNumber); 162 | Util.SetHalf(ref loaded.rom[index / 2 + RomAddress.Gfx], val, index % 2 == 0); 163 | index += 1; 164 | } 165 | } 166 | else if (state == stateMap["__gff__"]) { 167 | for (var i = 0; i < line.Length; i += 2) { 168 | loaded.rom[RomAddress.GfxProps + index] = byte.Parse(line.Substring(i, 2), NumberStyles.HexNumber); 169 | index += 1; 170 | } 171 | } 172 | else if (state == stateMap["__map__"]) { 173 | for (var i = 0; i < line.Length; i += 2) { 174 | loaded.rom[RomAddress.Map + index] = byte.Parse(line.Substring(i, 2), NumberStyles.HexNumber); 175 | index += 1; 176 | } 177 | } 178 | else if (state == stateMap["__sfx__"]) { 179 | if (Regex.IsMatch(line, @"^\s*$")) { 180 | continue; 181 | } 182 | 183 | var editor = byte.Parse(line.Substring(0, 2), NumberStyles.HexNumber); 184 | var speed = byte.Parse(line.Substring(2, 2), NumberStyles.HexNumber); 185 | var startLoop = byte.Parse(line.Substring(4, 2), NumberStyles.HexNumber); 186 | var endLoop = byte.Parse(line.Substring(6, 2), NumberStyles.HexNumber); 187 | 188 | loaded.rom[RomAddress.Sfx + index * 68 + 64] = editor; 189 | loaded.rom[RomAddress.Sfx + index * 68 + 65] = speed; 190 | loaded.rom[RomAddress.Sfx + index * 68 + 66] = startLoop; 191 | loaded.rom[RomAddress.Sfx + index * 68 + 67] = endLoop; 192 | 193 | var off = 0; 194 | 195 | for (var i = 0; i < line.Length - 8; i += 5) { 196 | var pitch = byte.Parse(line.Substring(i + 8, 2), NumberStyles.HexNumber); 197 | var waveform = byte.Parse(line.Substring(i + 8 + 2, 1), NumberStyles.HexNumber); 198 | var volume = byte.Parse(line.Substring(i + 8 + 3, 1), NumberStyles.HexNumber); 199 | var effect = byte.Parse(line.Substring(i + 8 + 4, 1), NumberStyles.HexNumber); 200 | 201 | var lo = (byte)(pitch | (waveform << 6)); 202 | var hi = (byte)((waveform >> 2) | (volume << 1) | (effect << 4)); 203 | 204 | loaded.rom[RomAddress.Sfx + index * 68 + off] = lo; 205 | loaded.rom[RomAddress.Sfx + index * 68 + off + 1] = hi; 206 | off += 2; 207 | } 208 | 209 | index += 1; 210 | } 211 | else if (state == stateMap["__music__"]) { 212 | if (Regex.IsMatch(line, @"^\s*$")) { 213 | continue; 214 | } 215 | 216 | var flag = byte.Parse(line.Substring(0, 2), NumberStyles.HexNumber); 217 | var val1 = byte.Parse(line.Substring(3, 2), NumberStyles.HexNumber); 218 | var val2 = byte.Parse(line.Substring(5, 2), NumberStyles.HexNumber); 219 | var val3 = byte.Parse(line.Substring(7, 2), NumberStyles.HexNumber); 220 | var val4 = byte.Parse(line.Substring(9, 2), NumberStyles.HexNumber); 221 | 222 | // 4th byte never has 7th bit set because it's corresponding flag value is never used. 223 | if ((flag & 0x1) != 0) { 224 | val1 |= 0x80; 225 | } 226 | 227 | if ((flag & 0x2) != 0) { 228 | val2 |= 0x80; 229 | } 230 | 231 | if ((flag & 0x4) != 0) { 232 | val3 |= 0x80; 233 | } 234 | 235 | loaded.rom[RomAddress.Song + index + 0] = val1; 236 | loaded.rom[RomAddress.Song + index + 1] = val2; 237 | loaded.rom[RomAddress.Song + index + 2] = val3; 238 | loaded.rom[RomAddress.Song + index + 3] = val4; 239 | 240 | index += 4; 241 | } 242 | } 243 | 244 | loaded.code = LuaPatcher.PatchCode(codeBuilder.ToString()); 245 | streamReader.Close(); 246 | 247 | return true; 248 | } 249 | 250 | public void SaveP8(string filename = null) { 251 | if (loaded == null) { 252 | return; 253 | } 254 | 255 | if (filename == null) { 256 | filename = loaded.path; 257 | } 258 | 259 | var fs = new FileStream(filename, FileMode.OpenOrCreate); 260 | 261 | using (var file = new StreamWriter(fs)) { 262 | file.WriteLine("pico-8 cartridge // http://www.pico-8.com"); 263 | file.WriteLine($"version {loaded.rom[RomAddress.Meta]}"); 264 | 265 | file.WriteLine("__lua__"); 266 | file.WriteLine(loaded.code); 267 | 268 | file.WriteLine("__gfx__"); 269 | 270 | for (var j = 0; j < 128; j += 1) { 271 | for (var i = 0; i < 64; i += 1) { 272 | var left = Util.GetHalf(loaded.rom[j * 64 + i + RomAddress.Gfx], false); 273 | var right = Util.GetHalf(loaded.rom[j * 64 + i + RomAddress.Gfx]); 274 | file.Write($"{right:x}{left:x}"); 275 | } 276 | 277 | file.Write("\n"); 278 | } 279 | 280 | file.WriteLine("__gff__"); 281 | 282 | for (var j = 0; j < 2; j += 1) { 283 | for (var i = 0; i < 128; i += 1) { 284 | var left = Util.GetHalf(loaded.rom[j * 128 + i + RomAddress.GfxProps], false); 285 | var right = Util.GetHalf(loaded.rom[j * 128 + i + RomAddress.GfxProps]); 286 | file.Write($"{left:x}{right:x}"); 287 | } 288 | 289 | file.Write("\n"); 290 | } 291 | 292 | file.WriteLine("__map__"); 293 | 294 | for (var j = 0; j < 64; j += 1) { 295 | for (var i = 0; i < 64; i += 1) { 296 | var left = Util.GetHalf(loaded.rom[j * 64 + i + RomAddress.Map], false); 297 | var right = Util.GetHalf(loaded.rom[j * 64 + i + RomAddress.Map]); 298 | file.Write($"{left:x}{right:x}"); 299 | } 300 | 301 | file.Write("\n"); 302 | } 303 | 304 | file.WriteLine("__sfx__"); 305 | 306 | for (var j = 0; j < 64; j += 1) { 307 | var editor = loaded.rom[RomAddress.Sfx + j * 68 + 64]; 308 | var speed = loaded.rom[RomAddress.Sfx + j * 68 + 65]; 309 | var startLoop = loaded.rom[RomAddress.Sfx + j * 68 + 66]; 310 | var endLoop = loaded.rom[RomAddress.Sfx + j * 68 + 67]; 311 | 312 | file.Write( 313 | $"{editor.ToString("x2")}{speed.ToString("x2")}{startLoop.ToString("x2")}{endLoop.ToString("x2")}"); 314 | 315 | for (var i = 0; i < 64; i += 2) { 316 | var lo = loaded.rom[RomAddress.Sfx + j * 68 + i]; 317 | var high = loaded.rom[RomAddress.Sfx + j * 68 + i + 1]; 318 | 319 | var pitch = (byte)(lo & 0b00111111); 320 | var waveform = (byte)(((lo & 0b11000000) >> 6) | ((high & 0b1) << 2)); 321 | var volume = (byte)((high & 0b00001110) >> 1); 322 | var effect = (byte)((high & 0b01110000) >> 4); 323 | 324 | file.Write($"{pitch:x2}{waveform:x}{volume:x}{effect:x}"); 325 | } 326 | 327 | file.Write("\n"); 328 | } 329 | 330 | file.WriteLine("__music__"); 331 | 332 | for (var j = 0; j < 64; j += 1) { 333 | byte flag = 0; 334 | var val0 = loaded.rom[j * 4 + 0 + RomAddress.Song]; 335 | var val1 = loaded.rom[j * 4 + 1 + RomAddress.Song]; 336 | var val2 = loaded.rom[j * 4 + 2 + RomAddress.Song]; 337 | var val3 = loaded.rom[j * 4 + 3 + RomAddress.Song]; 338 | 339 | if ((val0 & 0x80) == 0x80) { 340 | flag |= 1; 341 | val0 &= 0x7F; 342 | } 343 | 344 | if ((val1 & 0x80) == 0x80) { 345 | flag |= 2; 346 | val1 &= 0x7F; 347 | } 348 | 349 | if ((val2 & 0x80) == 0x80) { 350 | flag |= 4; 351 | val2 &= 0x7F; 352 | } 353 | 354 | file.Write($"{flag:D2} {val0:x2}{val1:x2}{val2:x2}{val3:x2}\n"); 355 | } 356 | 357 | file.Close(); 358 | } 359 | } 360 | 361 | public void Import(string filename, bool onlyHalf = false) { 362 | var sheet = new Bitmap(filename); 363 | 364 | if (sheet.Height != 128 || sheet.Width != 128) { 365 | throw new ArgumentException($"{filename} must be a 128x128 image, but is {sheet.Width}x{sheet.Width}."); 366 | } 367 | 368 | var div = onlyHalf ? 2 : 1; 369 | 370 | for (var i = 0; i < sheet.Height / div; i += 1) { 371 | for (var j = 0; j < sheet.Width; j += 1) { 372 | byte val = Palette.ColorToPalette(sheet.GetPixel(j, i)); 373 | Emulator.Graphics.Sset(j, i, val); 374 | } 375 | } 376 | 377 | sheet.Dispose(); 378 | } 379 | 380 | public void Export(string filename) { 381 | var sheet = new Bitmap(128, 128); 382 | 383 | for (var i = 0; i < sheet.Height; i += 1) { 384 | for (var j = 0; j < sheet.Width; j += 1) { 385 | var val = Emulator.Graphics.Sget(j, i); 386 | 387 | sheet.SetPixel(j, i, Color.FromArgb(Palette.standardPalette[val, 0], 388 | Palette.standardPalette[val, 1], 389 | Palette.standardPalette[val, 2])); 390 | } 391 | } 392 | 393 | if (File.Exists(filename)) { 394 | File.Delete(filename); 395 | } 396 | 397 | sheet.Save(filename, ImageFormat.Png); 398 | sheet.Dispose(); 399 | } 400 | 401 | public void Cartdata(string id) { 402 | Trace.Assert(loaded.cartDataId.Length == 0, "cartdata() can only be called once"); 403 | Trace.Assert(id.Length <= 64, "cart data id too long"); 404 | Trace.Assert(id.Length != 0, "empty cart data id"); 405 | 406 | Trace.Assert(Regex.IsMatch(id, "^[a-zA-Z0-9_]*$"), "cart data id: bad char"); 407 | 408 | var fileName = Cartridge.CartDataPath + id; 409 | 410 | if (File.Exists(fileName)) { 411 | using (var reader = new BinaryReader(File.Open(fileName, FileMode.Open))) { 412 | for (var i = 0; i < Cartridge.CartDataSize; i++) { 413 | loaded.cartData[i] = reader.ReadInt32(); 414 | } 415 | } 416 | } 417 | else { 418 | for (var i = 0; i < Cartridge.CartDataSize; i++) { 419 | loaded.cartData[i] = 0; 420 | } 421 | 422 | SaveCartData(id); 423 | } 424 | 425 | loaded.cartDataId = id; 426 | } 427 | 428 | public object Dget(int index) { 429 | Trace.Assert(index < Cartridge.CartDataSize, "bad index"); 430 | 431 | return Util.FixedToFloat(loaded.cartData[index]); 432 | } 433 | 434 | public void Dset(int index, double value) { 435 | Trace.Assert(index < Cartridge.CartDataSize, "bad index"); 436 | 437 | loaded.cartData[index] = Util.FloatToFixed(value); 438 | SaveCartData(Cartridge.CartDataPath + loaded.cartDataId); 439 | } 440 | 441 | private void SaveCartData(string fileName) { 442 | using (BinaryWriter writer = new BinaryWriter(File.Open(fileName, FileMode.Create))) { 443 | for (int i = 0; i < Cartridge.CartDataSize; i++) { 444 | writer.Write(loaded.cartData[i]); 445 | } 446 | } 447 | } 448 | 449 | public void Reload(int dest_addr, int source_addr, int len, string filename = "") { 450 | Trace.Assert(dest_addr < 0x4300); 451 | 452 | // FIXME 453 | var cart = loaded; // filename.Length == 0 ? Loaded : new Cartridge(filename); 454 | Emulator.Memory.Memcpy(dest_addr, source_addr, len, cart.rom); 455 | } 456 | 457 | public void Cstore(int dest_addr, int source_addr, int len, string filename = null) { 458 | Trace.Assert(dest_addr < 0x4300); 459 | 460 | // FIXME 461 | var cart = loaded; // filename == null ? loadedGame.cartridge : new Cartridge(filename, true); 462 | Buffer.BlockCopy(Emulator.Memory.ram, source_addr, cart.rom, dest_addr, len); 463 | SaveP8(); 464 | } 465 | 466 | public void Run() { 467 | loaded.cartData = new int[Cartridge.CartDataSize]; 468 | loaded.interpreter = new MoonSharpInterpreter(); 469 | 470 | Emulator.Memory.LoadCartridgeData(loaded.rom); 471 | 472 | Emulator.InitApi(loaded.interpreter); 473 | 474 | foreach (var u in Emulator.units) { 475 | u.OnCartridgeLoad(); 476 | } 477 | 478 | loaded.interpreter.RunScript(loaded.code); 479 | 480 | Emulator.Graphics.Flip(); 481 | 482 | _startTime = DateTime.Now; 483 | HighFps = loaded.interpreter.IsDefined("_update60"); 484 | loaded.interpreter.CallIfDefined("_init"); 485 | } 486 | 487 | public double Time() { 488 | return (DateTime.Now - _startTime).TotalSeconds; 489 | } 490 | } 491 | } -------------------------------------------------------------------------------- /Pico8Emulator/unit/graphics/Font.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Pico8Emulator.unit.graphics { 4 | /* 5 | * TODO: use hex instead of this 6 | * 000 = 0x0 7 | * 111 = 0x9 8 | * etc 9 | */ 10 | public static class Font { 11 | public static Dictionary dictionary; 12 | 13 | #region digit definitions 14 | private static byte[,] _empty = { 15 | {0, 0, 0}, 16 | {0, 0, 0}, 17 | {0, 0, 0}, 18 | {0, 0, 0}, 19 | {0, 0, 0}, 20 | }; 21 | 22 | private static byte[,] _exclamation = { 23 | {0, 1, 0}, 24 | {0, 1, 0}, 25 | {0, 1, 0}, 26 | {0, 0, 0}, 27 | {0, 1, 0}, 28 | }; 29 | 30 | private static byte[,] _quotes = { 31 | {1, 0, 1}, 32 | {1, 0, 1}, 33 | {0, 0, 0}, 34 | {0, 0, 0}, 35 | {0, 0, 0}, 36 | }; 37 | 38 | private static byte[,] _hashtag = { 39 | {1, 0, 1}, 40 | {1, 1, 1}, 41 | {1, 0, 1}, 42 | {1, 1, 1}, 43 | {1, 0, 1}, 44 | }; 45 | 46 | private static byte[,] _dolar = { 47 | {1, 1, 1}, 48 | {1, 1, 0}, 49 | {0, 1, 1}, 50 | {1, 1, 1}, 51 | {0, 1, 0}, 52 | }; 53 | 54 | private static byte[,] _percentage = { 55 | {1, 0, 1}, 56 | {0, 0, 1}, 57 | {0, 1, 0}, 58 | {1, 0, 0}, 59 | {1, 0, 1}, 60 | }; 61 | 62 | private static byte[,] _ampersand = { 63 | {1, 1, 0}, 64 | {1, 1, 0}, 65 | {1, 1, 0}, 66 | {1, 0, 1}, 67 | {1, 1, 1}, 68 | }; 69 | 70 | private static byte[,] _tone = { 71 | {0, 1, 0}, 72 | {1, 0, 0}, 73 | {0, 0, 0}, 74 | {0, 0, 0}, 75 | {0, 0, 0}, 76 | }; 77 | 78 | private static byte[,] _par_open = { 79 | {0, 1, 0}, 80 | {1, 0, 0}, 81 | {1, 0, 0}, 82 | {1, 0, 0}, 83 | {0, 1, 0}, 84 | }; 85 | 86 | private static byte[,] _par_close = { 87 | {0, 1, 0}, 88 | {0, 0, 1}, 89 | {0, 0, 1}, 90 | {0, 0, 1}, 91 | {0, 1, 0}, 92 | }; 93 | 94 | private static byte[,] _astherisc = { 95 | {1, 0, 1}, 96 | {0, 1, 0}, 97 | {1, 1, 1}, 98 | {0, 1, 0}, 99 | {1, 0, 1}, 100 | }; 101 | 102 | private static byte[,] _plus = { 103 | {0, 0, 0}, 104 | {0, 1, 0}, 105 | {1, 1, 1}, 106 | {0, 1, 0}, 107 | {0, 0, 0}, 108 | }; 109 | 110 | private static byte[,] _comma = { 111 | {0, 0, 0}, 112 | {0, 0, 0}, 113 | {0, 0, 0}, 114 | {0, 1, 0}, 115 | {1, 0, 0}, 116 | }; 117 | 118 | private static byte[,] _dash = { 119 | {0, 0, 0}, 120 | {0, 0, 0}, 121 | {1, 1, 1}, 122 | {0, 0, 0}, 123 | {0, 0, 0}, 124 | }; 125 | 126 | private static byte[,] _dot = { 127 | {0, 0, 0}, 128 | {0, 0, 0}, 129 | {0, 0, 0}, 130 | {0, 0, 0}, 131 | {0, 1, 0}, 132 | }; 133 | 134 | private static byte[,] _slash = { 135 | {0, 0, 1}, 136 | {0, 1, 0}, 137 | {0, 1, 0}, 138 | {0, 1, 0}, 139 | {1, 0, 0}, 140 | }; 141 | 142 | private static byte[,] _digit0 = { 143 | {1, 1, 1}, 144 | {1, 0, 1}, 145 | {1, 0, 1}, 146 | {1, 0, 1}, 147 | {1, 1, 1}, 148 | }; 149 | 150 | private static byte[,] _digit1 = { 151 | {1, 1, 0}, 152 | {0, 1, 0}, 153 | {0, 1, 0}, 154 | {0, 1, 0}, 155 | {1, 1, 1}, 156 | }; 157 | 158 | private static byte[,] _digit2 = { 159 | {1, 1, 1}, 160 | {0, 0, 1}, 161 | {1, 1, 1}, 162 | {1, 0, 0}, 163 | {1, 1, 1}, 164 | }; 165 | 166 | private static byte[,] _digit3 = { 167 | {1, 1, 1}, 168 | {0, 0, 1}, 169 | {0, 1, 1}, 170 | {0, 0, 1}, 171 | {1, 1, 1}, 172 | }; 173 | 174 | private static byte[,] _digit4 = { 175 | {1, 0, 1}, 176 | {1, 0, 1}, 177 | {1, 1, 1}, 178 | {0, 0, 1}, 179 | {0, 0, 1}, 180 | }; 181 | 182 | private static byte[,] _digit5 = { 183 | {1, 1, 1}, 184 | {1, 0, 0}, 185 | {1, 1, 1}, 186 | {0, 0, 1}, 187 | {1, 1, 1}, 188 | }; 189 | 190 | private static byte[,] _digit6 = { 191 | {1, 0, 0}, 192 | {1, 0, 0}, 193 | {1, 1, 1}, 194 | {1, 0, 1}, 195 | {1, 1, 1}, 196 | }; 197 | 198 | private static byte[,] _digit7 = { 199 | {1, 1, 1}, 200 | {0, 0, 1}, 201 | {0, 0, 1}, 202 | {0, 0, 1}, 203 | {0, 0, 1}, 204 | }; 205 | 206 | private static byte[,] _digit8 = { 207 | {1, 1, 1}, 208 | {1, 0, 1}, 209 | {1, 1, 1}, 210 | {1, 0, 1}, 211 | {1, 1, 1}, 212 | }; 213 | 214 | private static byte[,] _digit9 = { 215 | {1, 1, 1}, 216 | {1, 0, 1}, 217 | {1, 1, 1}, 218 | {0, 0, 1}, 219 | {0, 0, 1}, 220 | }; 221 | 222 | private static byte[,] _colon = { 223 | {0, 0, 0}, 224 | {0, 1, 0}, 225 | {0, 0, 0}, 226 | {0, 1, 0}, 227 | {0, 0, 0}, 228 | }; 229 | 230 | private static byte[,] _semicolon = { 231 | {0, 0, 0}, 232 | {0, 1, 0}, 233 | {0, 0, 0}, 234 | {0, 1, 0}, 235 | {1, 0, 0}, 236 | }; 237 | 238 | private static byte[,] _less = { 239 | {0, 0, 1}, 240 | {0, 1, 0}, 241 | {1, 0, 0}, 242 | {0, 1, 0}, 243 | {0, 0, 1}, 244 | }; 245 | 246 | private static byte[,] _equals = { 247 | {0, 0, 0}, 248 | {1, 1, 1}, 249 | {0, 0, 0}, 250 | {1, 1, 1}, 251 | {0, 0, 0}, 252 | }; 253 | 254 | private static byte[,] _greater = { 255 | {1, 0, 0}, 256 | {0, 1, 0}, 257 | {0, 0, 1}, 258 | {0, 1, 0}, 259 | {1, 0, 0}, 260 | }; 261 | 262 | private static byte[,] _question = { 263 | {1, 1, 1}, 264 | {0, 0, 1}, 265 | {0, 1, 1}, 266 | {0, 0, 0}, 267 | {0, 1, 0}, 268 | }; 269 | 270 | private static byte[,] _at = { 271 | {0, 1, 0}, 272 | {1, 0, 1}, 273 | {1, 0, 1}, 274 | {1, 0, 0}, 275 | {0, 1, 1}, 276 | }; 277 | 278 | private static byte[,] _a = { 279 | {0, 0, 0}, 280 | {1, 1, 1}, 281 | {1, 0, 1}, 282 | {1, 1, 1}, 283 | {1, 0, 1}, 284 | }; 285 | 286 | private static byte[,] _b = { 287 | {0, 0, 0}, 288 | {1, 1, 0}, 289 | {1, 1, 0}, 290 | {1, 0, 1}, 291 | {1, 1, 1}, 292 | }; 293 | 294 | private static byte[,] _c = { 295 | {0, 0, 0}, 296 | {1, 1, 1}, 297 | {1, 0, 0}, 298 | {1, 0, 0}, 299 | {1, 1, 1}, 300 | }; 301 | 302 | private static byte[,] _d = { 303 | {0, 0, 0}, 304 | {1, 1, 0}, 305 | {1, 0, 1}, 306 | {1, 0, 1}, 307 | {1, 1, 0}, 308 | }; 309 | 310 | private static byte[,] _e = { 311 | {0, 0, 0}, 312 | {1, 1, 1}, 313 | {1, 1, 0}, 314 | {1, 0, 0}, 315 | {1, 1, 1}, 316 | }; 317 | 318 | private static byte[,] _f = { 319 | {0, 0, 0}, 320 | {1, 1, 1}, 321 | {1, 1, 0}, 322 | {1, 0, 0}, 323 | {1, 0, 0}, 324 | }; 325 | 326 | private static byte[,] _g = { 327 | {0, 0, 0}, 328 | {1, 1, 1}, 329 | {1, 0, 0}, 330 | {1, 0, 1}, 331 | {1, 1, 1}, 332 | }; 333 | 334 | private static byte[,] _h = { 335 | {0, 0, 0}, 336 | {1, 0, 1}, 337 | {1, 0, 1}, 338 | {1, 1, 1}, 339 | {1, 0, 1}, 340 | }; 341 | 342 | private static byte[,] _i = { 343 | {0, 0, 0}, 344 | {1, 1, 1}, 345 | {0, 1, 0}, 346 | {0, 1, 0}, 347 | {1, 1, 1}, 348 | }; 349 | 350 | private static byte[,] _j = { 351 | {0, 0, 0}, 352 | {1, 1, 1}, 353 | {0, 1, 0}, 354 | {0, 1, 0}, 355 | {1, 1, 0}, 356 | }; 357 | 358 | private static byte[,] _k = { 359 | {0, 0, 0}, 360 | {1, 0, 1}, 361 | {1, 1, 0}, 362 | {1, 0, 1}, 363 | {1, 0, 1}, 364 | }; 365 | 366 | private static byte[,] _l = { 367 | {0, 0, 0}, 368 | {1, 0, 0}, 369 | {1, 0, 0}, 370 | {1, 0, 0}, 371 | {1, 1, 1}, 372 | }; 373 | 374 | private static byte[,] _m = { 375 | {0, 0, 0}, 376 | {1, 1, 1}, 377 | {1, 1, 1}, 378 | {1, 0, 1}, 379 | {1, 0, 1}, 380 | }; 381 | 382 | private static byte[,] _n = { 383 | {0, 0, 0}, 384 | {1, 1, 0}, 385 | {1, 0, 1}, 386 | {1, 0, 1}, 387 | {1, 0, 1}, 388 | }; 389 | 390 | private static byte[,] _o = { 391 | {0, 0, 0}, 392 | {0, 1, 1}, 393 | {1, 0, 1}, 394 | {1, 0, 1}, 395 | {1, 1, 0}, 396 | }; 397 | 398 | private static byte[,] _p = { 399 | {0, 0, 0}, 400 | {1, 1, 1}, 401 | {1, 0, 1}, 402 | {1, 1, 1}, 403 | {1, 0, 0}, 404 | }; 405 | 406 | private static byte[,] _q = { 407 | {0, 0, 0}, 408 | {0, 1, 0}, 409 | {1, 0, 1}, 410 | {1, 1, 0}, 411 | {0, 1, 1}, 412 | }; 413 | 414 | private static byte[,] _r = { 415 | {0, 0, 0}, 416 | {1, 1, 1}, 417 | {1, 0, 1}, 418 | {1, 1, 0}, 419 | {1, 0, 1}, 420 | }; 421 | 422 | private static byte[,] _s = { 423 | {0, 0, 0}, 424 | {0, 1, 1}, 425 | {1, 0, 0}, 426 | {0, 0, 1}, 427 | {1, 1, 0}, 428 | }; 429 | 430 | private static byte[,] _t = { 431 | {0, 0, 0}, 432 | {1, 1, 1}, 433 | {0, 1, 0}, 434 | {0, 1, 0}, 435 | {0, 1, 0}, 436 | }; 437 | 438 | private static byte[,] _u = { 439 | {0, 0, 0}, 440 | {1, 0, 1}, 441 | {1, 0, 1}, 442 | {1, 0, 1}, 443 | {0, 1, 1}, 444 | }; 445 | 446 | private static byte[,] _v = { 447 | {0, 0, 0}, 448 | {1, 0, 1}, 449 | {1, 0, 1}, 450 | {1, 1, 1}, 451 | {0, 1, 0}, 452 | }; 453 | 454 | private static byte[,] _w = { 455 | {0, 0, 0}, 456 | {1, 0, 1}, 457 | {1, 0, 1}, 458 | {1, 1, 1}, 459 | {1, 1, 1}, 460 | }; 461 | 462 | private static byte[,] _x = { 463 | {0, 0, 0}, 464 | {1, 0, 1}, 465 | {0, 1, 0}, 466 | {1, 0, 1}, 467 | {1, 0, 1}, 468 | }; 469 | 470 | private static byte[,] _y = { 471 | {0, 0, 0}, 472 | {1, 0, 1}, 473 | {1, 1, 1}, 474 | {0, 0, 1}, 475 | {1, 1, 1}, 476 | }; 477 | 478 | private static byte[,] _z = { 479 | {0, 0, 0}, 480 | {1, 1, 1}, 481 | {0, 0, 1}, 482 | {1, 0, 0}, 483 | {1, 1, 1}, 484 | }; 485 | 486 | private static byte[,] _bracket_open = { 487 | {1, 1, 0}, 488 | {1, 0, 0}, 489 | {1, 0, 0}, 490 | {1, 0, 0}, 491 | {1, 1, 0}, 492 | }; 493 | 494 | private static byte[,] _backslash = { 495 | {1, 0, 0}, 496 | {0, 1, 0}, 497 | {0, 1, 0}, 498 | {0, 1, 0}, 499 | {0, 0, 1}, 500 | }; 501 | 502 | private static byte[,] _bracket_close = { 503 | {0, 1, 1}, 504 | {0, 0, 1}, 505 | {0, 0, 1}, 506 | {0, 0, 1}, 507 | {0, 1, 1}, 508 | }; 509 | 510 | private static byte[,] _carat = { 511 | {0, 1, 0}, 512 | {1, 0, 1}, 513 | {0, 0, 0}, 514 | {0, 0, 0}, 515 | {0, 0, 0}, 516 | }; 517 | 518 | private static byte[,] _underscore = { 519 | {0, 0, 0}, 520 | {0, 0, 0}, 521 | {0, 0, 0}, 522 | {0, 0, 0}, 523 | {1, 1, 1}, 524 | }; 525 | 526 | private static byte[,] _back_quote = { 527 | {0, 1, 0}, 528 | {0, 0, 1}, 529 | {0, 0, 0}, 530 | {0, 0, 0}, 531 | {0, 0, 0}, 532 | }; 533 | 534 | private static byte[,] _A = { 535 | {1, 1, 1}, 536 | {1, 0, 1}, 537 | {1, 1, 1}, 538 | {1, 0, 1}, 539 | {1, 0, 1}, 540 | }; 541 | 542 | private static byte[,] _B = { 543 | {1, 1, 1}, 544 | {1, 0, 1}, 545 | {1, 1, 0}, 546 | {1, 0, 1}, 547 | {1, 1, 1}, 548 | }; 549 | 550 | private static byte[,] _C = { 551 | {0, 1, 1}, 552 | {1, 0, 0}, 553 | {1, 0, 0}, 554 | {1, 0, 0}, 555 | {0, 1, 1}, 556 | }; 557 | 558 | private static byte[,] _D = { 559 | {1, 1, 0}, 560 | {1, 0, 1}, 561 | {1, 0, 1}, 562 | {1, 0, 1}, 563 | {1, 1, 1}, 564 | }; 565 | 566 | private static byte[,] _E = { 567 | {1, 1, 1}, 568 | {1, 0, 0}, 569 | {1, 1, 0}, 570 | {1, 0, 0}, 571 | {1, 1, 1}, 572 | }; 573 | 574 | private static byte[,] _F = { 575 | {1, 1, 1}, 576 | {1, 0, 0}, 577 | {1, 1, 0}, 578 | {1, 0, 0}, 579 | {1, 0, 0}, 580 | }; 581 | 582 | private static byte[,] _G = { 583 | {0, 1, 1}, 584 | {1, 0, 0}, 585 | {1, 0, 0}, 586 | {1, 0, 1}, 587 | {1, 1, 1}, 588 | }; 589 | 590 | private static byte[,] _H = { 591 | {1, 0, 1}, 592 | {1, 0, 1}, 593 | {1, 1, 1}, 594 | {1, 0, 1}, 595 | {1, 0, 1}, 596 | }; 597 | 598 | private static byte[,] _I = { 599 | {1, 1, 1}, 600 | {0, 1, 0}, 601 | {0, 1, 0}, 602 | {0, 1, 0}, 603 | {1, 1, 1}, 604 | }; 605 | 606 | private static byte[,] J = { 607 | {1, 1, 1}, 608 | {0, 1, 0}, 609 | {0, 1, 0}, 610 | {0, 1, 0}, 611 | {1, 1, 0}, 612 | }; 613 | 614 | private static byte[,] _K = { 615 | {1, 0, 1}, 616 | {1, 0, 1}, 617 | {1, 1, 0}, 618 | {1, 0, 1}, 619 | {1, 0, 1}, 620 | }; 621 | 622 | private static byte[,] _L = { 623 | {1, 0, 0}, 624 | {1, 0, 0}, 625 | {1, 0, 0}, 626 | {1, 0, 0}, 627 | {1, 1, 1}, 628 | }; 629 | 630 | private static byte[,] _M = { 631 | {1, 1, 1}, 632 | {1, 1, 1}, 633 | {1, 0, 1}, 634 | {1, 0, 1}, 635 | {1, 0, 1}, 636 | }; 637 | 638 | private static byte[,] _N = { 639 | {1, 1, 0}, 640 | {1, 0, 1}, 641 | {1, 0, 1}, 642 | {1, 0, 1}, 643 | {1, 0, 1}, 644 | }; 645 | 646 | private static byte[,] _O = { 647 | {0, 1, 1}, 648 | {1, 0, 1}, 649 | {1, 0, 1}, 650 | {1, 0, 1}, 651 | {1, 1, 0}, 652 | }; 653 | 654 | private static byte[,] _P = { 655 | {1, 1, 1}, 656 | {1, 0, 1}, 657 | {1, 1, 1}, 658 | {1, 0, 0}, 659 | {1, 0, 0}, 660 | }; 661 | 662 | private static byte[,] _Q = { 663 | {0, 1, 0}, 664 | {1, 0, 1}, 665 | {1, 0, 1}, 666 | {1, 1, 0}, 667 | {0, 1, 1}, 668 | }; 669 | 670 | private static byte[,] _R = { 671 | {1, 1, 1}, 672 | {1, 0, 1}, 673 | {1, 1, 0}, 674 | {1, 0, 1}, 675 | {1, 0, 1}, 676 | }; 677 | 678 | private static byte[,] _S = { 679 | {0, 1, 1}, 680 | {1, 0, 0}, 681 | {1, 1, 1}, 682 | {0, 0, 1}, 683 | {1, 1, 0}, 684 | }; 685 | 686 | private static byte[,] _T = { 687 | {1, 1, 1}, 688 | {0, 1, 0}, 689 | {0, 1, 0}, 690 | {0, 1, 0}, 691 | {0, 1, 0}, 692 | }; 693 | 694 | private static byte[,] _U = { 695 | {1, 0, 1}, 696 | {1, 0, 1}, 697 | {1, 0, 1}, 698 | {1, 0, 1}, 699 | {0, 1, 1}, 700 | }; 701 | 702 | private static byte[,] _V = { 703 | {1, 0, 1}, 704 | {1, 0, 1}, 705 | {1, 0, 1}, 706 | {1, 0, 1}, 707 | {0, 1, 0}, 708 | }; 709 | 710 | private static byte[,] _W = { 711 | {1, 0, 1}, 712 | {1, 0, 1}, 713 | {1, 0, 1}, 714 | {1, 1, 1}, 715 | {1, 1, 1}, 716 | }; 717 | 718 | private static byte[,] _X = { 719 | {1, 0, 1}, 720 | {1, 0, 1}, 721 | {0, 1, 0}, 722 | {1, 0, 1}, 723 | {1, 0, 1}, 724 | }; 725 | 726 | private static byte[,] _Y = { 727 | {1, 0, 1}, 728 | {1, 0, 1}, 729 | {1, 1, 1}, 730 | {0, 0, 1}, 731 | {1, 1, 1}, 732 | }; 733 | 734 | private static byte[,] _Z = { 735 | {1, 1, 1}, 736 | {0, 0, 1}, 737 | {0, 1, 0}, 738 | {1, 0, 0}, 739 | {1, 1, 1}, 740 | }; 741 | 742 | private static byte[,] _brace_open = { 743 | {0, 1, 1}, 744 | {0, 1, 0}, 745 | {1, 1, 0}, 746 | {0, 1, 0}, 747 | {0, 1, 1}, 748 | }; 749 | 750 | private static byte[,] _pipe = { 751 | {0, 1, 0}, 752 | {0, 1, 0}, 753 | {0, 1, 0}, 754 | {0, 1, 0}, 755 | {0, 1, 0}, 756 | }; 757 | 758 | private static byte[,] _brace_close = { 759 | {1, 1, 0}, 760 | {0, 1, 0}, 761 | {0, 1, 1}, 762 | {0, 1, 0}, 763 | {1, 1, 0}, 764 | }; 765 | 766 | private static byte[,] _tilde = { 767 | {0, 0, 0}, 768 | {0, 0, 1}, 769 | {1, 1, 1}, 770 | {1, 0, 0}, 771 | {0, 0, 0}, 772 | }; 773 | 774 | private static byte[,] _nubbin = { 775 | {0, 0, 0}, 776 | {0, 1, 0}, 777 | {1, 0, 1}, 778 | {1, 0, 1}, 779 | {1, 1, 1}, 780 | }; 781 | 782 | private static byte[,] _block = { 783 | {1, 1, 1, 1, 1, 1, 1}, 784 | {1, 1, 1, 1, 1, 1, 1}, 785 | {1, 1, 1, 1, 1, 1, 1}, 786 | {1, 1, 1, 1, 1, 1, 1}, 787 | {1, 1, 1, 1, 1, 1, 1}, 788 | }; 789 | 790 | private static byte[,] _semi_block = { 791 | {1, 0, 1, 0, 1, 0, 1}, 792 | {0, 1, 0, 1, 0, 1, 0}, 793 | {1, 0, 1, 0, 1, 0, 1}, 794 | {0, 1, 0, 1, 0, 1, 0}, 795 | {1, 0, 1, 0, 1, 0, 1}, 796 | }; 797 | 798 | private static byte[,] _alien = { 799 | {1, 0, 0, 0, 0, 0, 1}, 800 | {1, 1, 1, 1, 1, 1, 1}, 801 | {1, 0, 1, 1, 1, 0, 1}, 802 | {1, 0, 1, 1, 1, 0, 1}, 803 | {0, 1, 1, 1, 1, 1, 0}, 804 | }; 805 | 806 | private static byte[,] _downbutton = { 807 | {0, 1, 1, 1, 1, 1, 0}, 808 | {1, 1, 0, 0, 0, 1, 1}, 809 | {1, 1, 0, 0, 0, 1, 1}, 810 | {1, 1, 1, 0, 1, 1, 1}, 811 | {0, 1, 1, 1, 1, 1, 0}, 812 | }; 813 | 814 | private static byte[,] _quasi_block = { 815 | {1, 0, 0, 0, 1, 0, 0}, 816 | {0, 0, 1, 0, 0, 0, 1}, 817 | {1, 0, 0, 0, 1, 0, 0}, 818 | {0, 0, 1, 0, 0, 0, 1}, 819 | {1, 0, 0, 0, 1, 0, 0}, 820 | }; 821 | 822 | private static byte[,] _shuriken = { 823 | {0, 0, 1, 0, 0, 0, 0}, 824 | {0, 0, 1, 1, 1, 1, 0}, 825 | {0, 0, 1, 1, 1, 0, 0}, 826 | {0, 1, 1, 1, 1, 0, 0}, 827 | {0, 0, 0, 0, 1, 0, 0}, 828 | }; 829 | 830 | private static byte[,] _shiny_ball = { 831 | {0, 0, 1, 1, 1, 0, 0}, 832 | {0, 1, 1, 1, 0, 1, 0}, 833 | {0, 1, 1, 1, 1, 1, 0}, 834 | {0, 1, 1, 1, 1, 1, 0}, 835 | {0, 0, 1, 1, 1, 0, 0}, 836 | }; 837 | 838 | private static byte[,] _heart = { 839 | {0, 1, 1, 0, 1, 1, 0}, 840 | {0, 1, 1, 1, 1, 1, 0}, 841 | {0, 1, 1, 1, 1, 1, 0}, 842 | {0, 0, 1, 1, 1, 0, 0}, 843 | {0, 0, 0, 1, 0, 0, 0}, 844 | }; 845 | 846 | private static byte[,] _sauron = { 847 | {0, 0, 1, 1, 1, 0, 0}, 848 | {0, 1, 1, 0, 1, 1, 0}, 849 | {1, 1, 1, 0, 1, 1, 1}, 850 | {0, 1, 1, 0, 1, 1, 0}, 851 | {0, 0, 1, 1, 1, 0, 0}, 852 | }; 853 | 854 | private static byte[,] _human = { 855 | {0, 0, 1, 1, 1, 0, 0}, 856 | {0, 0, 1, 1, 1, 0, 0}, 857 | {0, 1, 1, 1, 1, 1, 0}, 858 | {0, 0, 1, 1, 1, 0, 0}, 859 | {0, 0, 1, 0, 1, 0, 0}, 860 | }; 861 | 862 | private static byte[,] _house = { 863 | {0, 0, 1, 1, 1, 0, 0}, 864 | {0, 1, 1, 1, 1, 1, 0}, 865 | {1, 1, 1, 1, 1, 1, 1}, 866 | {0, 1, 0, 1, 0, 1, 0}, 867 | {0, 1, 0, 1, 1, 1, 0}, 868 | }; 869 | 870 | private static byte[,] _leftbutton = { 871 | {0, 1, 1, 1, 1, 1, 0}, 872 | {1, 1, 1, 0, 0, 1, 1}, 873 | {1, 1, 0, 0, 0, 1, 1}, 874 | {1, 1, 1, 0, 0, 1, 1}, 875 | {0, 1, 1, 1, 1, 1, 0}, 876 | }; 877 | 878 | private static byte[,] _face = { 879 | {1, 1, 1, 1, 1, 1, 1}, 880 | {1, 0, 1, 1, 1, 0, 1}, 881 | {1, 1, 1, 1, 1, 1, 1}, 882 | {1, 0, 0, 0, 0, 0, 1}, 883 | {1, 1, 1, 1, 1, 1, 1}, 884 | }; 885 | 886 | private static byte[,] _note = { 887 | {0, 0, 0, 1, 1, 1, 0}, 888 | {0, 0, 0, 1, 0, 0, 0}, 889 | {0, 0, 0, 1, 0, 0, 0}, 890 | {0, 1, 1, 1, 0, 0, 0}, 891 | {0, 1, 1, 1, 0, 0, 0}, 892 | }; 893 | 894 | private static byte[,] _obutton = { 895 | {0, 1, 1, 1, 1, 1, 0}, 896 | {1, 1, 0, 0, 0, 1, 1}, 897 | {1, 1, 0, 1, 0, 1, 1}, 898 | {1, 1, 0, 0, 0, 1, 1}, 899 | {0, 1, 1, 1, 1, 1, 0}, 900 | }; 901 | 902 | private static byte[,] _diamond = { 903 | {0, 0, 0, 1, 0, 0, 0}, 904 | {0, 0, 1, 1, 1, 0, 0}, 905 | {0, 1, 1, 1, 1, 1, 0}, 906 | {0, 0, 1, 1, 1, 0, 0}, 907 | {0, 0, 0, 1, 0, 0, 0}, 908 | }; 909 | 910 | private static byte[,] _dot_line = { 911 | {0, 0, 0, 0, 0, 0, 0}, 912 | {0, 0, 0, 0, 0, 0, 0}, 913 | {1, 0, 1, 0, 1, 0, 1}, 914 | {0, 0, 0, 0, 0, 0, 0}, 915 | {0, 0, 0, 0, 0, 0, 0}, 916 | }; 917 | 918 | private static byte[,] _rightbutton = { 919 | {0, 1, 1, 1, 1, 1, 0}, 920 | {1, 1, 0, 0, 1, 1, 1}, 921 | {1, 1, 0, 0, 0, 1, 1}, 922 | {1, 1, 0, 0, 1, 1, 1}, 923 | {0, 1, 1, 1, 1, 1, 0}, 924 | }; 925 | 926 | private static byte[,] _star = { 927 | {0, 0, 0, 1, 0, 0, 0}, 928 | {0, 0, 1, 1, 1, 0, 0}, 929 | {1, 1, 1, 1, 1, 1, 1}, 930 | {0, 1, 1, 1, 1, 1, 0}, 931 | {0, 1, 0, 0, 0, 1, 0}, 932 | }; 933 | 934 | private static byte[,] _hourclass = { 935 | {0, 1, 1, 1, 1, 1, 0}, 936 | {0, 0, 1, 1, 1, 0, 0}, 937 | {0, 0, 0, 1, 0, 0, 0}, 938 | {0, 0, 1, 1, 1, 0, 0}, 939 | {0, 1, 1, 1, 1, 1, 0}, 940 | }; 941 | 942 | private static byte[,] _upbutton = { 943 | {0, 1, 1, 1, 1, 1, 0}, 944 | {1, 1, 1, 0, 1, 1, 1}, 945 | {1, 1, 0, 0, 0, 1, 1}, 946 | {1, 1, 0, 0, 0, 1, 1}, 947 | {0, 1, 1, 1, 1, 1, 0}, 948 | }; 949 | 950 | private static byte[,] _down_arrows = { 951 | {0, 0, 0, 0, 0, 0, 0}, 952 | {1, 0, 1, 0, 0, 0, 0}, 953 | {0, 1, 0, 0, 1, 0, 1}, 954 | {0, 0, 0, 0, 0, 1, 0}, 955 | {0, 0, 0, 0, 0, 0, 0}, 956 | }; 957 | 958 | private static byte[,] _triangle_wave = { 959 | {0, 0, 0, 0, 0, 0, 0}, 960 | {1, 0, 0, 0, 1, 0, 0}, 961 | {0, 1, 0, 1, 0, 1, 0}, 962 | {0, 0, 1, 0, 0, 0, 1}, 963 | {0, 0, 0, 0, 0, 0, 0}, 964 | }; 965 | 966 | private static byte[,] _xbutton = { 967 | {0, 1, 1, 1, 1, 1, 0}, 968 | {1, 1, 0, 1, 0, 1, 1}, 969 | {1, 1, 1, 0, 1, 1, 1}, 970 | {1, 1, 0, 1, 0, 1, 1}, 971 | {0, 1, 1, 1, 1, 1, 0}, 972 | }; 973 | 974 | private static byte[,] _horizontal_lines = { 975 | {1, 1, 1, 1, 1, 1, 1}, 976 | {0, 0, 0, 0, 0, 0, 0}, 977 | {1, 1, 1, 1, 1, 1, 1}, 978 | {0, 0, 0, 0, 0, 0, 0}, 979 | {1, 1, 1, 1, 1, 1, 1}, 980 | }; 981 | 982 | private static byte[,] _vertical_lines = { 983 | {1, 0, 1, 0, 1, 0, 1}, 984 | {1, 0, 1, 0, 1, 0, 1}, 985 | {1, 0, 1, 0, 1, 0, 1}, 986 | {1, 0, 1, 0, 1, 0, 1}, 987 | {1, 0, 1, 0, 1, 0, 1}, 988 | }; 989 | #endregion 990 | 991 | static Font() { 992 | dictionary = new Dictionary(); 993 | dictionary.Add(' ', _empty); 994 | dictionary.Add('!', _exclamation); 995 | dictionary.Add('"', _quotes); 996 | dictionary.Add('#', _hashtag); 997 | dictionary.Add('$', _dolar); 998 | dictionary.Add('%', _percentage); 999 | dictionary.Add('&', _ampersand); 1000 | dictionary.Add('\'', _tone); 1001 | dictionary.Add('(', _par_open); 1002 | dictionary.Add(')', _par_close); 1003 | dictionary.Add('*', _astherisc); 1004 | dictionary.Add('+', _plus); 1005 | dictionary.Add(',', _comma); 1006 | dictionary.Add('-', _dash); 1007 | dictionary.Add('.', _dot); 1008 | dictionary.Add('/', _slash); 1009 | dictionary.Add('0', _digit0); 1010 | dictionary.Add('1', _digit1); 1011 | dictionary.Add('2', _digit2); 1012 | dictionary.Add('3', _digit3); 1013 | dictionary.Add('4', _digit4); 1014 | dictionary.Add('5', _digit5); 1015 | dictionary.Add('6', _digit6); 1016 | dictionary.Add('7', _digit7); 1017 | dictionary.Add('8', _digit8); 1018 | dictionary.Add('9', _digit9); 1019 | dictionary.Add(':', _colon); 1020 | dictionary.Add(';', _semicolon); 1021 | dictionary.Add('<', _less); 1022 | dictionary.Add('=', _equals); 1023 | dictionary.Add('>', _greater); 1024 | dictionary.Add('?', _question); 1025 | dictionary.Add('@', _at); 1026 | dictionary.Add('a', _a); 1027 | dictionary.Add('b', _b); 1028 | dictionary.Add('c', _c); 1029 | dictionary.Add('d', _d); 1030 | dictionary.Add('e', _e); 1031 | dictionary.Add('f', _f); 1032 | dictionary.Add('g', _g); 1033 | dictionary.Add('h', _h); 1034 | dictionary.Add('i', _i); 1035 | dictionary.Add('j', _j); 1036 | dictionary.Add('k', _k); 1037 | dictionary.Add('l', _l); 1038 | dictionary.Add('m', _m); 1039 | dictionary.Add('n', _n); 1040 | dictionary.Add('o', _o); 1041 | dictionary.Add('p', _p); 1042 | dictionary.Add('q', _q); 1043 | dictionary.Add('r', _r); 1044 | dictionary.Add('s', _s); 1045 | dictionary.Add('t', _t); 1046 | dictionary.Add('u', _u); 1047 | dictionary.Add('v', _v); 1048 | dictionary.Add('w', _w); 1049 | dictionary.Add('x', _x); 1050 | dictionary.Add('y', _y); 1051 | dictionary.Add('z', _z); 1052 | dictionary.Add('[', _bracket_open); 1053 | dictionary.Add('\\', _backslash); 1054 | dictionary.Add(']', _bracket_close); 1055 | dictionary.Add('^', _carat); 1056 | dictionary.Add('_', _underscore); 1057 | dictionary.Add('`', _back_quote); 1058 | dictionary.Add('A', _A); 1059 | dictionary.Add('B', _B); 1060 | dictionary.Add('C', _C); 1061 | dictionary.Add('D', _D); 1062 | dictionary.Add('E', _E); 1063 | dictionary.Add('F', _F); 1064 | dictionary.Add('G', _G); 1065 | dictionary.Add('H', _H); 1066 | dictionary.Add('I', _I); 1067 | dictionary.Add('J', J); 1068 | dictionary.Add('K', _K); 1069 | dictionary.Add('L', _L); 1070 | dictionary.Add('M', _M); 1071 | dictionary.Add('N', _N); 1072 | dictionary.Add('O', _O); 1073 | dictionary.Add('P', _P); 1074 | dictionary.Add('Q', _Q); 1075 | dictionary.Add('R', _R); 1076 | dictionary.Add('S', _S); 1077 | dictionary.Add('T', _T); 1078 | dictionary.Add('U', _U); 1079 | dictionary.Add('V', _V); 1080 | dictionary.Add('W', _W); 1081 | dictionary.Add('X', _X); 1082 | dictionary.Add('Y', _Y); 1083 | dictionary.Add('Z', _Z); 1084 | dictionary.Add('{', _brace_open); 1085 | dictionary.Add('|', _pipe); 1086 | dictionary.Add('}', _brace_close); 1087 | dictionary.Add('~', _tilde); 1088 | dictionary.Add((char)127, _nubbin); 1089 | dictionary.Add((char)128, _block); 1090 | dictionary.Add((char)129, _semi_block); 1091 | dictionary.Add((char)130, _alien); 1092 | dictionary.Add((char)131, _downbutton); 1093 | dictionary.Add((char)132, _quasi_block); 1094 | dictionary.Add((char)133, _shuriken); 1095 | dictionary.Add((char)134, _shiny_ball); 1096 | dictionary.Add((char)135, _heart); 1097 | dictionary.Add((char)136, _sauron); 1098 | dictionary.Add((char)137, _human); 1099 | dictionary.Add((char)138, _house); 1100 | dictionary.Add((char)139, _leftbutton); 1101 | dictionary.Add((char)140, _face); 1102 | dictionary.Add((char)141, _note); 1103 | dictionary.Add((char)142, _obutton); 1104 | dictionary.Add((char)143, _diamond); 1105 | dictionary.Add((char)144, _dot_line); 1106 | dictionary.Add((char)145, _rightbutton); 1107 | dictionary.Add((char)146, _star); 1108 | dictionary.Add((char)147, _hourclass); 1109 | dictionary.Add((char)148, _upbutton); 1110 | dictionary.Add((char)149, _down_arrows); 1111 | dictionary.Add((char)150, _triangle_wave); 1112 | dictionary.Add((char)151, _xbutton); 1113 | dictionary.Add((char)152, _horizontal_lines); 1114 | dictionary.Add((char)153, _vertical_lines); 1115 | } 1116 | } 1117 | } -------------------------------------------------------------------------------- /MonoGamePico8/testcarts/jelpi.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 4 3 | __lua__ 4 | -- the adventures of jelpi 5 | -- by zep 6 | 7 | -- to do: 8 | -- levels and monsters 9 | -- title / restart logic 10 | -- block loot 11 | -- top-solid ground 12 | -- better duping 13 | 14 | -- config: num_players 1 or 2 15 | num_players = 1 16 | corrupt_mode = false 17 | max_actors = 128 18 | 19 | music(0, 0, 3) 20 | 21 | 22 | function make_actor(k,x,y,d) 23 | local a = {} 24 | a.kind = k 25 | a.life = 1 26 | a.x=x a.y=y a.dx=0 a.dy=0 27 | a.ddy = 0.06 -- gravity 28 | a.w=0.3 a.h=0.5 -- half-width 29 | a.d=d a.bounce=0.8 30 | a.frame = 1 a.f0 = 0 31 | a.t=0 32 | a.standing = false 33 | if (count(actor) < max_actors) then 34 | add(actor, a) 35 | end 36 | return a 37 | end 38 | 39 | function make_sparkle(x,y,frame,col) 40 | local s = {} 41 | s.x=x 42 | s.y=y 43 | s.frame=frame 44 | s.col=col 45 | s.t=0 s.max_t = 8+rnd(4) 46 | s.dx = 0 s.dy = 0 47 | s.ddy = 0 48 | add(sparkle,s) 49 | return s 50 | end 51 | 52 | function make_player(x, y, d) 53 | pl = make_actor(1, x, y, d) 54 | pl.charge = 0 55 | pl.super = 0 56 | pl.score = 0 57 | pl.bounce = 0 58 | pl.delay = 0 59 | pl.id = 0 -- player 1 60 | pl.pal = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} 61 | 62 | return pl 63 | end 64 | 65 | -- called at start by pico-8 66 | function _init() 67 | 68 | actor = {} 69 | sparkle = {} 70 | 71 | -- spawn player 72 | for y=0,63 do for x=0,127 do 73 | if (mget(x,y) == 48) then 74 | player = make_player(x,y+1,1) 75 | 76 | if (num_players==2) then 77 | player2 = make_player(x+2,y+1,1) 78 | player2.id = 1 79 | player2.pal = {1,3,3,4,5,6,7,11,9,10,11,12,13,15,7} 80 | end 81 | 82 | end 83 | end end 84 | t = 0 85 | 86 | death_t = 0 87 | end 88 | 89 | -- clear_cel using neighbour val 90 | -- prefer empty, then non-ground 91 | -- then left neighbour 92 | function clear_cel(x, y) 93 | val0 = mget(x-1,y) 94 | val1 = mget(x+1,y) 95 | if (val0 == 0 or val1 == 0) then 96 | mset(x,y,0) 97 | elseif (not fget(val1,1)) then 98 | mset(x,y,val1) 99 | else 100 | mset(x,y,val0) 101 | end 102 | end 103 | 104 | 105 | function move_spawns(x0, y0) 106 | 107 | -- spawn stuff close to x0,y0 108 | 109 | for y=0,32 do 110 | for x=x0-10,x0+10 do 111 | val = mget(x,y) 112 | m = nil 113 | 114 | -- pickup 115 | if (fget(val, 5)) then 116 | m = make_actor(2,x+0.5,y+1,1) 117 | m.f0 = val 118 | m.frame = val 119 | if (fget(val,4)) then 120 | m.ddy = 0 -- zero gravity 121 | end 122 | end 123 | 124 | -- monster 125 | if (fget(val, 3)) then 126 | m = make_actor(3,x+0.5,y+1,-1) 127 | m.f0=val 128 | m.frame=val 129 | end 130 | 131 | -- clear cel if spawned something 132 | if (m ~= nil) then 133 | clear_cel(x,y) 134 | end 135 | end 136 | end 137 | 138 | end 139 | 140 | -- test if a point is solid 141 | function solid (x, y) 142 | if (x < 0 or x >= 128 ) then 143 | return true end 144 | 145 | val = mget(x, y) 146 | return fget(val, 1) 147 | end 148 | 149 | function move_pickup(a) 150 | a.frame = a.f0 151 | -- if (flr((t/4) % 2) == 0) then 152 | -- a.frame = a.f0+1 153 | -- end 154 | end 155 | 156 | function move_player(pl) 157 | 158 | local b = pl.id 159 | 160 | if (pl.life == 0) then 161 | death_t = 1 162 | for i=1,32 do 163 | s=make_sparkle( 164 | pl.x, pl.y-0.6, 96, 0) 165 | s.dx = cos(i/32)/2 166 | s.dy = sin(i/32)/2 167 | s.max_t = 30 168 | s.ddy = 0.01 169 | s.frame=96+rnd(3) 170 | s.col = 7 171 | end 172 | 173 | del(actor,pl) 174 | 175 | sfx(16) 176 | music(-1) 177 | sfx(5) 178 | 179 | return 180 | end 181 | 182 | 183 | accel = 0.05 184 | if (pl.charge > 10) then 185 | accel = 0.08 186 | end 187 | 188 | if (not pl.standing) then 189 | accel = accel / 2 190 | end 191 | 192 | -- player control 193 | if (btn(0,b)) then 194 | pl.dx = pl.dx - accel; pl.d=-1 end 195 | if (btn(1,b)) then 196 | pl.dx = pl.dx + accel; pl.d=1 end 197 | 198 | if ((btn(4,b) or btn(2,b)) and 199 | -- solid(pl.x,pl.y)) then 200 | pl.standing) then 201 | pl.dy = -0.7 202 | sfx(8) 203 | end 204 | 205 | -- charge 206 | 207 | if (btn(5,b) and pl.charge == 0 208 | and pl.delay == 0) then 209 | pl.charge = 15 210 | 211 | pl.dx = pl.dx + pl.d * 0.4 212 | 213 | if (not pl.standing) then 214 | pl.dy = pl.dy - 0.2 215 | end 216 | 217 | sfx(11) 218 | 219 | end 220 | 221 | -- charging 222 | 223 | if (pl.charge > 0 or 224 | pl.super > 0) then 225 | pl.frame = 53 226 | 227 | if (abs(pl.dx) > 0.4 or 228 | abs(pl.dy) > 0.2 229 | ) then 230 | 231 | for i=1,3 do 232 | local s = make_sparkle( 233 | pl.x+pl.dx*i/3, 234 | pl.y+pl.dy*i/3 - 0.3, 235 | 96+rnd(3), (pl.t*3+i)%9+7) 236 | if (rnd(2) < 1) then 237 | s.col = 7 238 | end 239 | s.dx = -pl.dx*0.1 240 | s.dy = -0.05*i/4 241 | s.x = s.x + rnd(0.6)-0.3 242 | s.y = s.y + rnd(0.6)-0.3 243 | end 244 | end 245 | end 246 | 247 | pl.charge = max(0, pl.charge-1) 248 | if (pl.charge > 0) then 249 | pl.delay = 10 250 | else 251 | pl.delay = max(0,pl.delay-1) 252 | end 253 | 254 | pl.super = max(0, pl.super-1) 255 | 256 | -- frame 257 | 258 | if (pl.standing) then 259 | pl.f0 = (pl.f0+abs(pl.dx)*2+4) % 4 260 | else 261 | pl.f0 = (pl.f0+abs(pl.dx)/2+4) % 4 262 | end 263 | 264 | if (abs(pl.dx) < 0.1) 265 | then 266 | pl.frame=48 pl.f0=0 267 | else 268 | pl.frame = 49+flr(pl.f0) 269 | end 270 | 271 | if (pl == player2) then 272 | pl.frame = pl.frame +75-48 273 | end 274 | 275 | end 276 | 277 | function move_monster(m) 278 | m.dx = m.dx + m.d * 0.02 279 | 280 | m.f0 = (m.f0+abs(m.dx)*3+4) % 4 281 | m.frame = 112 + flr(m.f0) 282 | 283 | if (false and m.standing and rnd(100) < 1) 284 | then 285 | m.dy = -1 286 | end 287 | 288 | end 289 | 290 | function move_actor(pl) 291 | 292 | -- to do: replace with callbacks 293 | 294 | if (pl.kind == 1) then 295 | move_player(pl) 296 | end 297 | 298 | if (pl.kind == 2) then 299 | move_pickup(pl) 300 | end 301 | 302 | if (pl.kind == 3) then 303 | move_monster(pl) 304 | end 305 | 306 | pl.standing=false 307 | 308 | -- x movement 309 | 310 | x1 = pl.x + pl.dx + 311 | sgn(pl.dx) * 0.3 312 | 313 | local broke_block = false 314 | 315 | if(not solid(x1,pl.y-0.5)) then 316 | pl.x = pl.x + pl.dx 317 | else -- hit wall 318 | 319 | -- search for contact point 320 | while (not solid(pl.x + sgn(pl.dx)*0.3, pl.y-0.5)) do 321 | pl.x = pl.x + sgn(pl.dx) * 0.1 322 | end 323 | 324 | -- if charging, break block 325 | if (pl.charge ~= nil) then 326 | 327 | if (pl.charge > 0 or 328 | pl.super > 0) then 329 | val = mget(x1, pl.y-0.5,0) 330 | if (fget(val,4)) then 331 | clear_cel(x1,pl.y-0.5) 332 | sfx(10) 333 | broke_block = true 334 | 335 | -- make debris 336 | 337 | for by=0,1 do 338 | for bx=0,1 do 339 | s=make_sparkle( 340 | 0.25+flr(x1) + bx*0.5, 341 | 0.25+flr(pl.y-0.5) + by*0.5, 342 | 22, 0) 343 | s.dx = (bx-0.5)/4 344 | s.dy = (by-0.5)/4 345 | s.max_t = 30 346 | s.ddy = 0.02 347 | end 348 | end 349 | 350 | else 351 | if (abs(pl.dx) > 0.2) then 352 | sfx(12) -- thump 353 | end 354 | end 355 | 356 | -- bumping kills charge 357 | if (pl.charge < 20) then 358 | pl.charge = 0 359 | end 360 | 361 | end 362 | end 363 | 364 | -- bounce 365 | if (pl.super == 0 or 366 | not broke_block) then 367 | pl.dx = pl.dx * -0.5 368 | end 369 | 370 | if (pl.kind == 3) then 371 | pl.d = pl.d * -1 372 | pl.dx=0 373 | end 374 | 375 | end 376 | 377 | -- y movement 378 | 379 | if (pl.dy < 0) then 380 | -- going up 381 | 382 | if (solid(pl.x-0.2, pl.y+pl.dy-1) or 383 | solid(pl.x+0.2, pl.y+pl.dy-1)) 384 | then 385 | pl.dy=0 386 | 387 | -- search up for collision point 388 | while ( not ( 389 | solid(pl.x-0.2, pl.y-1) or 390 | solid(pl.x+0.2, pl.y-1))) 391 | do 392 | pl.y = pl.y - 0.01 393 | end 394 | 395 | else 396 | pl.y = pl.y + pl.dy 397 | end 398 | 399 | else 400 | 401 | -- going down 402 | if (solid(pl.x-0.2, pl.y+pl.dy) or 403 | solid(pl.x+0.2, pl.y+pl.dy)) then 404 | 405 | -- bounce 406 | if (pl.bounce > 0 and 407 | pl.dy > 0.2) 408 | then 409 | pl.dy = pl.dy * -pl.bounce 410 | else 411 | 412 | pl.standing=true 413 | pl.dy = 0 414 | 415 | end 416 | 417 | --snap down 418 | while (not ( 419 | solid(pl.x-0.2,pl.y) or 420 | solid(pl.x+0.2,pl.y) 421 | )) 422 | do pl.y = pl.y + 0.05 end 423 | 424 | --pop up even if bouncing 425 | while(solid(pl.x-0.2,pl.y-0.1)) do 426 | pl.y = pl.y - 0.05 end 427 | while(solid(pl.x+0.2,pl.y-0.1)) do 428 | pl.y = pl.y - 0.05 end 429 | 430 | else 431 | pl.y = pl.y + pl.dy 432 | end 433 | 434 | end 435 | 436 | 437 | -- gravity and friction 438 | pl.dy = pl.dy + pl.ddy 439 | pl.dy = pl.dy * 0.95 440 | 441 | -- x friction 442 | if (pl.standing) then 443 | pl.dx = pl.dx * 0.8 444 | else 445 | pl.dx = pl.dx * 0.9 446 | end 447 | 448 | -- counters 449 | pl.t = pl.t + 1 450 | end 451 | 452 | function collide_event(a1, a2) 453 | if(a1.kind==1) then 454 | if(a2.kind==2) then 455 | 456 | if (a2.frame==64) then 457 | a1.super = 120 458 | a1.dx = a1.dx * 2 459 | --a1.dy = a1.dy-0.1 460 | -- a1.standing = false 461 | sfx(13) 462 | end 463 | 464 | -- gem 465 | if (a2.frame==80) then 466 | a1.score = a1.score + 1 467 | sfx(9) 468 | end 469 | 470 | del(actor,a2) 471 | 472 | end 473 | 474 | -- charge or dupe monster 475 | 476 | if(a2.kind==3) then -- monster 477 | if(a1.charge > 0 or 478 | a1.super > 0 or 479 | (a1.y-a1.dy) < a2.y-0.7) then 480 | -- slow down player 481 | a1.dx = a1.dx * 0.7 482 | a1.dy = a1.dy * -0.7-- - 0.2 483 | 484 | -- explode 485 | for i=1,16 do 486 | s=make_sparkle( 487 | a2.x, a2.y-0.5, 96+rnd(3), 7) 488 | s.dx = s.dx + rnd(0.4)-0.2 489 | s.dy = s.dy + rnd(0.4)-0.2 490 | s.max_t = 30 491 | s.ddy = 0.01 492 | 493 | end 494 | 495 | -- kill monster 496 | -- to do: in move_monster 497 | sfx(14) 498 | del(actor,a2) 499 | 500 | else 501 | 502 | -- player death 503 | a1.life=0 504 | 505 | 506 | end 507 | end 508 | 509 | end 510 | end 511 | 512 | function move_sparkle(sp) 513 | if (sp.t > sp.max_t) then 514 | del(sparkle,sp) 515 | end 516 | 517 | sp.x = sp.x + sp.dx 518 | sp.y = sp.y + sp.dy 519 | sp.dy= sp.dy+ sp.ddy 520 | sp.t = sp.t + 1 521 | end 522 | 523 | 524 | function collide(a1, a2) 525 | if (a1==a2) then return end 526 | local dx = a1.x - a2.x 527 | local dy = a1.y - a2.y 528 | if (abs(dx) < a1.w+a2.w) then 529 | if (abs(dy) < a1.h+a2.h) then 530 | collide_event(a1, a2) 531 | end 532 | end 533 | end 534 | 535 | function collisions() 536 | 537 | for a1 in all(actor) do 538 | collide(player,a1) 539 | end 540 | 541 | if (player2 ~= nil) then 542 | for a1 in all(actor) do 543 | collide(player2,a1) 544 | end 545 | end 546 | 547 | end 548 | 549 | function outgame_logic() 550 | 551 | if (death_t > 0) then 552 | death_t = death_t + 1 553 | if (death_t > 30 and 554 | btn(4) or btn(5)) 555 | then 556 | music(-1) 557 | sfx(-1) 558 | sfx(0) 559 | dpal={0,1,1, 2,1,13,6, 560 | 4,4,9,3, 13,1,13,14} 561 | 562 | -- palette fade 563 | for i=0,40 do 564 | for j=1,15 do 565 | col = j 566 | for k=1,((i+(j%5))/4) do 567 | col=dpal[col] 568 | end 569 | pal(j,col,1) 570 | end 571 | flip() 572 | end 573 | 574 | -- restart cart end of slice 575 | run() 576 | end 577 | end 578 | end 579 | 580 | function _update() 581 | 582 | foreach(actor, move_actor) 583 | foreach(sparkle, move_sparkle) 584 | collisions() 585 | move_spawns(player.x, player.y) 586 | 587 | outgame_logic() 588 | 589 | if (corrupt_mode) then 590 | for i=1,5 do 591 | poke(rnd(0x8000),rnd(0x100)) 592 | end 593 | end 594 | 595 | t=t+1 596 | end 597 | 598 | function draw_sparkle(s) 599 | 600 | if (s.col > 0) then 601 | for i=1,15 do 602 | pal(i,s.col) 603 | end 604 | end 605 | 606 | spr(s.frame, s.x*8-4, s.y*8-4) 607 | 608 | pal() 609 | end 610 | 611 | function draw_actor(pl) 612 | 613 | if (pl.pal ~= nil) then 614 | for i=1,15 do 615 | -- pal(i, pl.pal[i]) 616 | end 617 | end 618 | 619 | if (pl.charge ~= nil and 620 | pl.charge > 0) then 621 | 622 | for i=2,15 do 623 | pal(i,7+((pl.t/2) % 8)) 624 | end 625 | -- pal(2,7) 626 | 627 | end 628 | 629 | if (pl.super ~= nil and 630 | pl.super > 0) then 631 | 632 | for i=2,15 do 633 | pal(i,6+((pl.t/2) % 2)) 634 | end 635 | 636 | end 637 | 638 | spr(pl.frame, 639 | pl.x*8-4, pl.y*8-8, 640 | 1, 1, pl.d < 0) 641 | 642 | pal() 643 | end 644 | 645 | function _draw() 646 | 647 | -- sky 648 | camera (0, 0) 649 | rectfill (0,0,127,127,12) 650 | --for y=1,7 do 651 | -- rect(0,63-y*2.5,127,63-y*2.5,6) end 652 | 653 | -- background 654 | 655 | -- sspr(88,0,8,8,0,0,128,128) 656 | 657 | -- sky gradient 658 | if (false) then 659 | for y=0,127 do 660 | col=sget(88,(y+(y%4)*6) / 16) 661 | line(0,y,127,y,col) 662 | end 663 | end 664 | 665 | -- clouds behind mountains 666 | local x = t / 8 667 | x = x % 128 668 | local y=0 669 | mapdraw(16, 32, -x, y, 16, 16, 0) 670 | mapdraw(16, 32, 128-x, y, 16, 16, 0) 671 | 672 | 673 | local bgcol = 13 -- mountains 674 | pal(5,bgcol) pal(2,bgcol) 675 | pal(13,6) -- highlights 676 | y = 0 677 | mapdraw (0, 32, 0, y, 16, 16, 0) 678 | pal() 679 | 680 | 681 | -- map and actors 682 | cam_x = mid(0,player.x*8-64,1024-128) 683 | 684 | if (player2 ~= nil) then 685 | cam_x = 686 | mid(0,player2.x*8-64,1024-128) / 2 + 687 | cam_x / 2 688 | end 689 | 690 | --cam_y = mid(0,player.y*6-40,128) 691 | cam_y = 84 692 | camera (cam_x,cam_y) 693 | pal(12,0) 694 | mapdraw (0,0,0,0,128,64,1) 695 | pal() 696 | foreach(sparkle, draw_sparkle) 697 | foreach(actor, draw_actor) 698 | 699 | -- forground map 700 | -- mapdraw (0,0,0,0,128,64,2) 701 | 702 | -- player score 703 | camera(0,0) 704 | color(7) 705 | 706 | if (death_t > 60) then 707 | print("press button to restart", 708 | 18-1,10-0,8+(t/4)%2) 709 | print("press button to restart", 710 | 18,10,7) 711 | end 712 | 713 | if (false) then 714 | cursor(0,2) 715 | print("actors:"..count(actor)) 716 | print("score:"..player.score) 717 | print(stat(1)) 718 | end 719 | end 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | __gfx__ 729 | 00000000000000004444444433b333b30000000000000000effffff7d66667d666666667d6666667cccccccccccccccc2000000025522552cc5ccccc20000000 730 | 000000000000000044444444333333330000000000eeee002effff7f5d66765d666666765d666676ccccccccccccccc55200000052255225c55555cc50000000 731 | 00000000007007004424444422222222000000000eeee7e022eeeeff55dd6655dddddd6655dddd66cccccccccccccc55252000002552255255555ccc20000000 732 | 0000000000077000444444444444444400000000eeeeeeee22eeeeff55dd6655dddddd6655dddd66ccccccccccccc5555252000052255225c555cccc52020000 733 | 0000000000077000444444444444444400070000eeeeeeee22eeeeff55dd6655dddddd6655dddd66cccccccccccccc5c2525200025522552cc5ccccc25252000 734 | 00000000007007004444424444444244007a70002222222222eeeeff55dd6655dddddd6655dddd66ccccccccccc55555525252005225522555cccccc52525000 735 | 00000000000000004444444444444444000700000002d000221111ef5111d651111111d6511111d6cccccccccc55c55525252520255225525ccccccc25252500 736 | 0000000000000000444444444444444400030000000de0002111111e11111d111111111d1111111dccccccccc55555555252525252255225cccccccc52525250 737 | 00067000555ddd661010122244440404000b00000e0aa000000000000000000000000000cc5cccc5000000000000000ddddd2525dddddddd000000002525252d 738 | 0006700055dd666711101222444404440b3b000000d99090000000000000000000000000c555555500000077000000dd5ddd5252dddddddd000000005252525d 739 | 00566700c5d6667c0100112444442400003b0000a9777d0000eff7000000000000000000555555550000077700000dd525d5dd252ddddd2500000000252525dd 740 | 00566700c5d6667c0111122244442400000b0000a97779a0002eef000000000000333300c5555555000077770000ddd252ddddd252dddd520000000052525ddd 741 | 05d66670cc5667cc0000122244440000000b3b000d7779a0002eef000000000003333330cc5ccc5c00777777000dddd5252ddd252525d5250000000025252ddd 742 | 05d66670cc5667cc0000112444444000000b30009099d00000211e0000000000333a33335555c5550777777700dddd52525d52525252525200000000525252dd 743 | 55dd6667ccc67ccc0001222224444400000b000000aa0e00000000000000000033a7a33355555555077777770dd5d525252525252525252500000000252525dd 744 | 555ddd66ccc67ccc0011111224444440000b0000000300000000000000000000333a33335555c55577777777dd5dd2525252525252525252000000005252525d 745 | 444244444444444444444444424242424242424200000000333333333333333333333333bbbbbbbb000000000000000ddddd2525dddddddd0000000000000000 746 | 444444242444244444444444222422442424242400000000333333333333b33333333333bbbbbbbb77000000000000dddddd5252dddddddd0000000000000000 747 | 4242424242424424444244444242422242424442000000003339a3333333bab3333333333333333377770000000000ddddd52525dddddddd0000000000000000 748 | 242424242422244444444424242424242424244400707000339a7a33333bbb333333333333333333777770000000ddddddd25252dddddddd0000000000000000 749 | 4442224242424244424442444244224242124242000e00003399a93333333b33333333333bb33b3377777700000dddddddd52525dddddddd0000000000000000 750 | 422424242424242444442424222444242422222400737000333993333b333333333333333bb333337777770000ddd5dddd525252dddddddd0000000000000000 751 | 4442424242424444424442424222424242421242000b000033333333333333333333333333333333777777700ddd5dddd5252525dddddddd0000000000000000 752 | 2424242424242424242424242424242424242424000b00003333333333333333333333333333333377777777d5ddddddd2525252dddddddd0000000000000000 753 | 00000000000000000f000f000f000f00000000000000000000bbbbbbbbbbbb003333333300333300777777770000777700000000000000000000000000000000 754 | 0f000f000f000f000ffffff00ffffff00f000f00000000000bbbbbbbbbbbbbb03333333303333330777777770000777700000000000000000000000000000000 755 | 0ffffff00ffffff00f1fff100f1fff100ffffff000000000bb333333333333bb3333333333333333777777770000777700000000000000000000000000000000 756 | 0f1fff100f1fff100effffe00effffe00f1fff1000077000b33333333333333b3333333333333b33777777770000777700000000000000000000000000000000 757 | 0effffe00effffe000222000002220000effffe0000770003333bb33333333333313313333333333777777777777777777777777777700000000000000000000 758 | 002220000022200000888f000f88800000222000000000003333bb3333bb3333313113133b333333777777777777777777777777777777000000000000000000 759 | 0088800000888f000f00000000000f000f888000000000003333333333bb33331311113133333333777777777777777777777777777777700000000000000000 760 | 00f0f00000f0000000000000000000000000f0000000000033333333333333331111111133333333777777777777777777777777777777770000000000000000 761 | 00777700006666000077770000777700e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000000eeeee000eeeee0000000000 762 | 07000070060000600700007007000070222222222222222200000000000000000000000000000000000000000eeeee000eeeee00eeeeeee0eeeeeee00eeeee00 763 | 700077076000770670099007700990072ff22fff2fff2f220000000000000000000000000000000000000000eeeefee0eeeeeee0ef1ff1e0ef1ff1e0eeeeeee0 764 | 70b077076000770670999907709999072f222f2f2f2f2f220000000000000000000000000000000000000000ef1ff1e0ef1ff1e0eeffffe0eeffffe0ef1ff1e0 765 | 70ae000760c0000679999997799999972f2f2f2f2fff2f220000000000000000000000000000000000000000eeffffe0eeffffe0eeccce00eeccce00eeffffe0 766 | 707c8007607e000670099007700990072fff2fff2f2f2ff20000000000000000000000000000000000000000eeccce00eeccce000077780008777000eeccce00 767 | 07000070060000600709907007099070222222222222222200000000000000000000000000000000000000000077700000777800008000000000080008777000 768 | 00777700006666000077770000777700e7e7e7e7e7e7e7e700000000000000000000000000000000000000000080800000800000000000000000000000008000 769 | 0000000000000000000bb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 770 | 000de000000ef0000000b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 771 | 00d17e0000ed7f000994399000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 772 | 0d1de7e00edef7f099a9979900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 773 | 0efed1d00f7fede0949999a9aaa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 774 | 00ef1d0000f7de0094499999a0aaaaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 775 | 000ed000000fe00009449990a0a0a0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 776 | 000000000000000000999900aaa000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 777 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 778 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 779 | 00000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 780 | 00077000007070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 781 | 00077000000700000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 782 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 783 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 784 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 785 | 00000000000000000077770000777700076767670076667000076700006767600000000000000000000000000000000000000000000000000000000000000000 786 | 0077770000777700077787700777e770000050000000500000005000000050000000000000000000000000000000000000000000000000000000000000000000 787 | 077787700777b7700777777007777770007777000077770000777700007777000000000000000000000000000000000000000000000000000000000000000000 788 | 07777770077777700717771007177710077787700777877007778770077787700000000000000000000000000000000000000000000000000000000000000000 789 | 07177710071777100777777007777770071777100717771007177710071777100000000000000000000000000000000000000000000000000000000000000000 790 | 0777777007777770a999999009a99990077777700777777007777770077777700000000000000000000000000000000000000000000000000000000000000000 791 | 0999999a0999999000000a000000000a099999900999999009999990099999900000000000000000000000000000000000000000000000000000000000000000 792 | 00a000000a0000a0000000000000000000a00a0000a000a00a0000a00a000a000000000000000000000000000000000000000000000000000000000000000000 793 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 794 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 795 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 796 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 797 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 798 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 799 | 00000000000000000000000000000000000000000000000000000000000000000000c3d300c3c300d30000c3d300050000000000000000000000000000000000 800 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 801 | 0000000000000000000000000000000000000000000000000000000000000000000000a300a3b300a30000a3b300c30000000000000000000000000000000000 802 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 803 | 0000000000000000000000000000000000000000000000000000000000000000000000a300a3c300a3c300a30000a30000000000000000000000000000000000 804 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 805 | 00000000000000b1f00000000000000000000000a1a200000000000000000000000000a300c3c3c3c3c3c3c3c3c3c30000000000000000000000000000000000 806 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 807 | 0000b1c00000b2c2d0f0b1f00000000000a1a2a1a3a3a200000000000000000000a3a3a300000000000000000000000000000000000000000000000000000000 808 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 809 | c0b2c2d0f0b2d2c2d0d0c1d0f00000b1a1a3a3a3a3a3a3a2a1a20000a1a200000000000000000000000000000000000000000000000000000000000000000000 810 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 811 | d0d2c2d0d0d1d1d0d0d0d0d0d0c0b2c2a3a3a3a3a3a3a3a3a3a3a2a1a3a3a2a10000000000000000000000000000000000000000000000000000000000000000 812 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 813 | d0d1d0d0d0d0d0d0d0d0d0d0d0d0d1d0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a30000000000000000000000000000000000000000000000000000000000000000 814 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 815 | d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a30000000000000000000000000000000000000000000000000000000000000000 816 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 817 | d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a30000000000000000000000000000000000000000000000000000000000000000 818 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 819 | d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a30000000000000000000000000000000000000000000000000000000000000000 820 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 821 | d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a30000000000000000000000000000000000000000000000000000000000000000 822 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 823 | d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a30000000000000000000000000000000000000000000000000000000000000000 824 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 825 | 00000000009000900090009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 826 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 827 | 00000000009090909090909000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 828 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 829 | 00000000909090909090909090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 830 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c2c3c000000000000000000000000 831 | 00000000a0a0a005a005a0a0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 832 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d1d2d3d000000000000000000000000 833 | 00000000a0a0a0a0a0a0a0b091000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 834 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e1e2e3e000000000000000000000000 835 | 0000000090a0a0a007a0b09190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 836 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f1f2f3f000000000000000000000000 837 | 00000000909090909090909090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 838 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 839 | 00003030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 840 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 841 | 00000002020202020202020202020293000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 842 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 843 | 00828282828282606060608282828282000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 844 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 845 | 93828282828282606060608282828272000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 846 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 847 | 83838383838383606060608383838383000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 848 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 849 | 30303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 850 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 851 | 20202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 852 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 853 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 854 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 855 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 856 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 857 | __gff__ 858 | 0000030301811303030301010000010003030101010103030101000000000000030303030301010101030100000000000000000000000303010101000000000020002000010100000000000000000000200020200000000000000000000000000000000000000000000000000000000008000000000000000000000000000000 859 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 860 | __map__ 861 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 862 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 863 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 864 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 865 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 866 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 867 | 0000000000000000560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 868 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 869 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 870 | 0000000000000000000000000000000000000000000044440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 871 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500050005000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 872 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000505050505040000000000009000900090009000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 873 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009090708090909090900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 874 | 00000000000000000000000000000000000000000000000000000000004b4c00000000000000000000000000000000000000000000000000000000000009090a0a0a0a0a070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 875 | 00000000000000000000000000000000000000000000000000000000005b5c000000000000003629293700000000000000000000000015000000000000090a530a0a0a0a0a0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015000000 876 | 00000000000000000000000000000000000000000000000000000000000000000000000000002728262700000000000000000000000014250000000000090a0a0a0a520a0b1900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014150000 877 | 00000000000000000000000000000000000000000000000000000036292929293700000000002728272800000000000000000303030303030000000000090a0a0a700a0b190900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014140000 878 | 0000000000000000000000000000000000000000000000000000002827282726270000000000282728270000000000003918020222222202391800000009070809070809070800000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000044450000 879 | 0000000000000000000000000000000000000050000000000000002726282827280000000000383838380039183918392828222023232421282839391803030303030303030303030015000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000014140000 880 | 0000000000000000000000000000000000000000000000000000002828272827260000000070001213003938060638382828232324232323282728282820202320202020202020203914000000000000000000000000000000000000000000001500000000001415000000000000000000000000000000000603030303030303 881 | 0000000000000000000000000000000000000606060000000000002628282828270000000003030303030303030303032828282828282827282728282823232323232323232323232739000300000000000000000000000606000000000015001415000000151414000000000000000000000000000000030302020202020202 882 | 0015000000000000000000000000000000000000000000000000003838383838380000000002020202020222220202022828282828282728272828282828282828282828282828272828180203030303030300000070000606000000150014001414000000145014000000000000700000000000700000020202020202020202 883 | 2514000000000000000000000000000000001500000000000000000000121300000070000002022222222023232102023838383838383838383838383838380606060606060638383838380202020202020203030303030303007000140414001414000404141414000000000303030303030303030303020202020202020202 884 | 1414040030000000000000000600000025001400000000007000030303030303030303030302202323232323232321020303030303030303030303030303030303030303030303700370030202020202020202020202020202030303030303030303030303030303030303030202020202020202020202020202020202020202 885 | 0303030303030303030303030303030303030303030303030303020202020222020202022220232323232323232323212202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202 886 | 2222020202020202020202020202022222222222020202020202020202202323212222202323232323232323232323232321220202020222222202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202 887 | 2323212222222222222222222222202323232323212222020202022220232323232323232323232323232323232323232323232122222023232321222222222202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202 888 | 2323232323232323232323232323232324232323242323212121202323232323232323232323232323232323232323232323232323232323232323232323232322220202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202 889 | 2323232323232323232323232323242323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323230202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202000200020000000000 890 | 2323232323232324232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232302020202020202020202020202020202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000 891 | 2323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232302020202020202020202020202020202020202020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000 892 | 2323232323232323232323232323232323232323232323232323232323232323232323232323232302232323232302020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000 893 | __sfx__ 894 | 01030000185701c5701f57024570185701c5701f57024560185601c5601f56024560185501c5501f550245501a5501d5501f540245401a5301d5301f530235301a5301d5301f5301a5201d510215102451023515 895 | 01100000240452400528000280452b0450c005280450000529042240162d04500005307553c5252d000130052b0451f006260352b026260420c0052404500005230450c00521045230461f0450c0051c0421c025 896 | 01100000187451a7001c7001c7451d745187001c7451f7001a745247001d7451d70021745277002470023745217451f7001d7001d7451a7451b7001c7451f7001a745227001c7451b70018745187001f7451f700 897 | 01100000305453c52500600006003e625006000c30318600355250050000600006003e625006000060018600295263251529515006003e625006000060018600305250050018601006003e625246040060000600 898 | 01100000004750c47518475004750a475004750a4750c475004750a4750c475004750a4750c4751147513475004750c4750a475004750a475004750a4750c475004750c47516475004751647518475114750c475 899 | 01100000180721a0751b0721f0721e0751f0751e0721f075270752607724075200721f0751b0771a0751b07518072180621805218042180350000000000000000000000000000000000000000000000000000000 900 | 011000000c37518375243751f3751b3721a372193711b372183721837217371163511533114311133001830214302143021830218302003000030000300003000030000300003000030000300003000030000300 901 | 011000000c37300300003000030000300003000030000300003000030000300003000030000300003000030000300003000030000300003000030000300003000030000300003000030000300003000030000300 902 | 000000001e0701f070220702a020340103f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 903 | 000100002b7602e7503a73033740377302e75033730337303372035710377103a710337103a7103c7103c7003f700007000070000700007000070000700007000070000700007000070000700007000070000700 904 | 00020000276701d65013650106600c6400e63022620116300b63004630026101b6100861003610076101260013600106000d60010600116000e6001160012600116000a600066000960003600026000260002600 905 | 000100002257524575275652455527555275552b54524525225352252527525275252b5252e515305152e515305052e505305052e5053050530505335052b5052e5052b5052e5052e5053350530505335052e505 906 | 000200002005325043160231002304013030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 907 | 0102000013571165731b5751d5711157313575165711b5731b575225711b573185751b5711f573245751b5711f57324565295611f563185611d555245532b5552b5412b5433053137535335333a5212b5252e513 908 | 000200002b071270711b07118071100710b0710607104071040610606103061040510305101041010310102101011040110000000000000000000000000000000000000000000000000000000000000000000000 909 | 010200002e17029170171731a171231631d16111143141610c1230a11107110001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 910 | 01040000185702257024570225701f5701d5701f5701d57018570165701857016570135701157013570115700c5700d570135701457018560195501f550205302453024520225202452022510245102251024500 911 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 912 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 913 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 914 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 915 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 916 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 917 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 918 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 919 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 920 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 921 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 922 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 923 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 924 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 925 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 926 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 927 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 928 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 929 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 930 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 931 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 932 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 933 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 934 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 935 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 936 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 937 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 938 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 939 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 940 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 941 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 942 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 943 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 944 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 945 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 946 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 947 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 948 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 949 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 950 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 951 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 952 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 953 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 954 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 955 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 956 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 957 | 001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 958 | __music__ 959 | 01 01434144 960 | 00 02434144 961 | 00 01034244 962 | 02 02034244 963 | 00 41414144 964 | 00 41414144 965 | 00 41414144 966 | 00 41414144 967 | 00 41414144 968 | 00 41414144 969 | 00 41414144 970 | 00 41414144 971 | 00 41414144 972 | 00 41414144 973 | 00 41414144 974 | 00 41414144 975 | 00 41414144 976 | 00 41414144 977 | 00 41414144 978 | 00 41414144 979 | 00 41414144 980 | 00 41414144 981 | 00 41414144 982 | 00 41414144 983 | 00 41414144 984 | 00 41414144 985 | 00 41414144 986 | 00 41414144 987 | 00 41414144 988 | 00 41414144 989 | 00 41414144 990 | 00 41414144 991 | 00 41414144 992 | 00 41414144 993 | 00 41414144 994 | 00 41414144 995 | 00 41414144 996 | 00 41414144 997 | 00 41414144 998 | 00 41414144 999 | 00 41414144 1000 | 00 41414144 1001 | 00 41414144 1002 | 00 41414144 1003 | 00 41414144 1004 | 00 41414144 1005 | 00 41414144 1006 | 00 41414144 1007 | 00 41414144 1008 | 00 41414144 1009 | 00 41414144 1010 | 00 41414144 1011 | 00 41414144 1012 | 00 41414144 1013 | 00 41414144 1014 | 00 41414144 1015 | 00 41414144 1016 | 00 41414144 1017 | 00 41414144 1018 | 00 41414144 1019 | 00 41414144 1020 | 00 41414144 1021 | 00 41414144 1022 | 00 41414144 1023 | 1024 | --------------------------------------------------------------------------------