From 84cba789a36340e9843873abf6d356aa2329e186 Mon Sep 17 00:00:00 2001 From: Alessandro Proto Date: Sat, 7 Jan 2023 16:59:47 +0100 Subject: [PATCH] Add project files. --- .gitattributes | 63 + .gitignore | 363 ++++ Capy64.sln | 25 + Capy64/.config/dotnet-tools.json | 36 + Capy64/API/IPlugin.cs | 16 + Capy64/Assets/Lua/init.lua | 446 +++++ Capy64/Assets/Lua/lib/colors.lua | 93 + Capy64/Assets/Lua/lib/event.lua | 15 + Capy64/Assets/Lua/lib/expect.lua | 123 ++ Capy64/Assets/Lua/lib/utfstring.lua | 1762 +++++++++++++++++ Capy64/Assets/bios.lua | 122 ++ Capy64/Assets/font.ttf | Bin 0 -> 365304 bytes Capy64/BIOS/Bios.cs | 180 ++ Capy64/BIOS/PanicScreen.cs | 61 + Capy64/BIOS/RuntimeInputEvents.cs | 127 ++ Capy64/Capy64.cs | 171 ++ Capy64/Capy64.csproj | 46 + Capy64/Content/Content.mgcb | 15 + Capy64/Core/Audio.cs | 73 + Capy64/Core/Drawing.cs | 176 ++ Capy64/Eventing/EventEmitter.cs | 109 + Capy64/Eventing/Events/CharEvent.cs | 8 + Capy64/Eventing/Events/KeyEvent.cs | 14 + Capy64/Eventing/Events/MouseButtonEvent.cs | 13 + Capy64/Eventing/Events/MouseMoveEvent.cs | 10 + Capy64/Eventing/Events/MouseWheelEvent.cs | 11 + Capy64/Eventing/Events/TickEvent.cs | 14 + Capy64/Eventing/InputManager.cs | 282 +++ Capy64/Extensions/Bindings/SDL2.cs | 21 + Capy64/Extensions/GameWindowExtensions.cs | 62 + Capy64/IGame.cs | 31 + Capy64/Icon.bmp | Bin 0 -> 262282 bytes Capy64/Icon.ico | Bin 0 -> 147541 bytes Capy64/LuaRuntime/Constants.cs | 12 + Capy64/LuaRuntime/Extensions/Libraries.cs | 57 + .../LuaRuntime/Extensions/NativeLibraries.cs | 60 + Capy64/LuaRuntime/Extensions/Utils.cs | 102 + Capy64/LuaRuntime/Libraries/FileSystem.cs | 740 +++++++ Capy64/LuaRuntime/Libraries/Graphics.cs | 93 + Capy64/LuaRuntime/Libraries/HTTP.cs | 22 + Capy64/LuaRuntime/Libraries/OS.cs | 34 + Capy64/LuaRuntime/Libraries/Screen.cs | 104 + Capy64/LuaRuntime/Libraries/Term.cs | 574 ++++++ Capy64/LuaRuntime/Libraries/Timer.cs | 77 + Capy64/LuaRuntime/LuaEvent.cs | 8 + Capy64/LuaRuntime/LuaException.cs | 27 + Capy64/LuaRuntime/Runtime.cs | 137 ++ Capy64/LuaRuntime/Sandbox.cs | 304 +++ Capy64/PluginManager/PluginLoadContext.cs | 42 + Capy64/PluginManager/PluginLoader.cs | 44 + Capy64/Program.cs | 15 + Capy64/Properties/launchSettings.json | 11 + Capy64/Utils.cs | 21 + Capy64/Worker.cs | 62 + Capy64/app.manifest | 43 + 55 files changed, 7077 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Capy64.sln create mode 100644 Capy64/.config/dotnet-tools.json create mode 100644 Capy64/API/IPlugin.cs create mode 100644 Capy64/Assets/Lua/init.lua create mode 100644 Capy64/Assets/Lua/lib/colors.lua create mode 100644 Capy64/Assets/Lua/lib/event.lua create mode 100644 Capy64/Assets/Lua/lib/expect.lua create mode 100644 Capy64/Assets/Lua/lib/utfstring.lua create mode 100644 Capy64/Assets/bios.lua create mode 100644 Capy64/Assets/font.ttf create mode 100644 Capy64/BIOS/Bios.cs create mode 100644 Capy64/BIOS/PanicScreen.cs create mode 100644 Capy64/BIOS/RuntimeInputEvents.cs create mode 100644 Capy64/Capy64.cs create mode 100644 Capy64/Capy64.csproj create mode 100644 Capy64/Content/Content.mgcb create mode 100644 Capy64/Core/Audio.cs create mode 100644 Capy64/Core/Drawing.cs create mode 100644 Capy64/Eventing/EventEmitter.cs create mode 100644 Capy64/Eventing/Events/CharEvent.cs create mode 100644 Capy64/Eventing/Events/KeyEvent.cs create mode 100644 Capy64/Eventing/Events/MouseButtonEvent.cs create mode 100644 Capy64/Eventing/Events/MouseMoveEvent.cs create mode 100644 Capy64/Eventing/Events/MouseWheelEvent.cs create mode 100644 Capy64/Eventing/Events/TickEvent.cs create mode 100644 Capy64/Eventing/InputManager.cs create mode 100644 Capy64/Extensions/Bindings/SDL2.cs create mode 100644 Capy64/Extensions/GameWindowExtensions.cs create mode 100644 Capy64/IGame.cs create mode 100644 Capy64/Icon.bmp create mode 100644 Capy64/Icon.ico create mode 100644 Capy64/LuaRuntime/Constants.cs create mode 100644 Capy64/LuaRuntime/Extensions/Libraries.cs create mode 100644 Capy64/LuaRuntime/Extensions/NativeLibraries.cs create mode 100644 Capy64/LuaRuntime/Extensions/Utils.cs create mode 100644 Capy64/LuaRuntime/Libraries/FileSystem.cs create mode 100644 Capy64/LuaRuntime/Libraries/Graphics.cs create mode 100644 Capy64/LuaRuntime/Libraries/HTTP.cs create mode 100644 Capy64/LuaRuntime/Libraries/OS.cs create mode 100644 Capy64/LuaRuntime/Libraries/Screen.cs create mode 100644 Capy64/LuaRuntime/Libraries/Term.cs create mode 100644 Capy64/LuaRuntime/Libraries/Timer.cs create mode 100644 Capy64/LuaRuntime/LuaEvent.cs create mode 100644 Capy64/LuaRuntime/LuaException.cs create mode 100644 Capy64/LuaRuntime/Runtime.cs create mode 100644 Capy64/LuaRuntime/Sandbox.cs create mode 100644 Capy64/PluginManager/PluginLoadContext.cs create mode 100644 Capy64/PluginManager/PluginLoader.cs create mode 100644 Capy64/Program.cs create mode 100644 Capy64/Properties/launchSettings.json create mode 100644 Capy64/Utils.cs create mode 100644 Capy64/Worker.cs create mode 100644 Capy64/app.manifest diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/Capy64.sln b/Capy64.sln new file mode 100644 index 0000000..5b1d463 --- /dev/null +++ b/Capy64.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33110.190 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capy64", "Capy64\Capy64.csproj", "{D38FAB55-99A5-4C5B-9BF7-CC76443AC43A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D38FAB55-99A5-4C5B-9BF7-CC76443AC43A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D38FAB55-99A5-4C5B-9BF7-CC76443AC43A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D38FAB55-99A5-4C5B-9BF7-CC76443AC43A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D38FAB55-99A5-4C5B-9BF7-CC76443AC43A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {16085476-C90F-4033-8ACB-AC42E055010D} + EndGlobalSection +EndGlobal diff --git a/Capy64/.config/dotnet-tools.json b/Capy64/.config/dotnet-tools.json new file mode 100644 index 0000000..efabe22 --- /dev/null +++ b/Capy64/.config/dotnet-tools.json @@ -0,0 +1,36 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-mgcb": { + "version": "3.8.1.303", + "commands": [ + "mgcb" + ] + }, + "dotnet-mgcb-editor": { + "version": "3.8.1.303", + "commands": [ + "mgcb-editor" + ] + }, + "dotnet-mgcb-editor-linux": { + "version": "3.8.1.303", + "commands": [ + "mgcb-editor-linux" + ] + }, + "dotnet-mgcb-editor-windows": { + "version": "3.8.1.303", + "commands": [ + "mgcb-editor-windows" + ] + }, + "dotnet-mgcb-editor-mac": { + "version": "3.8.1.303", + "commands": [ + "mgcb-editor-mac" + ] + } + } +} \ No newline at end of file diff --git a/Capy64/API/IPlugin.cs b/Capy64/API/IPlugin.cs new file mode 100644 index 0000000..941b54c --- /dev/null +++ b/Capy64/API/IPlugin.cs @@ -0,0 +1,16 @@ +using KeraLua; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.API; + +public interface IPlugin +{ + void ConfigureServices(IServiceCollection services) { } + void LuaInit(Lua state) { } + +} diff --git a/Capy64/Assets/Lua/init.lua b/Capy64/Assets/Lua/init.lua new file mode 100644 index 0000000..d0a6f84 --- /dev/null +++ b/Capy64/Assets/Lua/init.lua @@ -0,0 +1,446 @@ +local term = require("term") +local timer = require("timer") +os.print = print + +package.path = "/lib/?.lua;/lib/?/init.lua;" .. package.path + +local event = require("event") +local colors = require("colors") +local expect = require("expect").expect + +function timer.sleep(n) + local timerId = timer.start(n) + repeat + local _, par = coroutine.yield("timer") + until par == timerId +end + +function write(sText) + expect(1, sText, "string", "number") + + local w, h = term.getSize() + local x, y = term.getPos() + + local nLinesPrinted = 0 + local function newLine() + if y + 1 <= h then + term.setPos(1, y + 1) + else + term.setPos(1, h) + term.scroll(1) + end + x, y = term.getPos() + nLinesPrinted = nLinesPrinted + 1 + end + + -- Print the line with proper word wrapping + sText = tostring(sText) + while #sText > 0 do + local whitespace = string.match(sText, "^[ \t]+") + if whitespace then + -- Print whitespace + term.write(whitespace) + x, y = term.getPos() + sText = string.sub(sText, #whitespace + 1) + end + + local newline = string.match(sText, "^\n") + if newline then + -- Print newlines + newLine() + sText = string.sub(sText, 2) + end + + local text = string.match(sText, "^[^ \t\n]+") + if text then + sText = string.sub(sText, #text + 1) + if #text > w then + -- Print a multiline word + while #text > 0 do + if x > w then + newLine() + end + term.write(text) + text = string.sub(text, w - x + 2) + x, y = term.getPos() + end + else + -- Print a word normally + if x + #text - 1 > w then + newLine() + end + term.write(text) + x, y = term.getPos() + end + end + end + + return nLinesPrinted +end + +function print(...) + local nLinesPrinted = 0 + local nLimit = select("#", ...) + for n = 1, nLimit do + local s = tostring(select(n, ...)) + if n < nLimit then + s = s .. "\t" + end + nLinesPrinted = nLinesPrinted + write(s) + end + nLinesPrinted = nLinesPrinted + write("\n") + return nLinesPrinted +end + +function read(_sReplaceChar, _tHistory, _fnComplete, _sDefault) + expect(1, _sReplaceChar, "string", "nil") + expect(2, _tHistory, "table", "nil") + expect(3, _fnComplete, "function", "nil") + expect(4, _sDefault, "string", "nil") + + term.setBlink(true) + + local sLine + if type(_sDefault) == "string" then + sLine = _sDefault + else + sLine = "" + end + local nHistoryPos + local nPos, nScroll = #sLine, 0 + if _sReplaceChar then + _sReplaceChar = string.sub(_sReplaceChar, 1, 1) + end + + local tCompletions + local nCompletion + local function recomplete() + if _fnComplete and nPos == #sLine then + tCompletions = _fnComplete(sLine) + if tCompletions and #tCompletions > 0 then + nCompletion = 1 + else + nCompletion = nil + end + else + tCompletions = nil + nCompletion = nil + end + end + + local function uncomplete() + tCompletions = nil + nCompletion = nil + end + + local w = term.getSize() + local sx = term.getPos() + + local function redraw(_bClear) + local cursor_pos = nPos - nScroll + if sx + cursor_pos >= w then + -- We've moved beyond the RHS, ensure we're on the edge. + nScroll = sx + nPos - w + elseif cursor_pos < 0 then + -- We've moved beyond the LHS, ensure we're on the edge. + nScroll = nPos + end + + local _, cy = term.getPos() + term.setPos(sx, cy) + local sReplace = _bClear and " " or _sReplaceChar + if sReplace then + term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0))) + else + term.write(string.sub(sLine, nScroll + 1)) + end + + if nCompletion then + local sCompletion = tCompletions[nCompletion] + local oldText, oldBg + if not _bClear then + oldText = term.getForeground() + oldBg = term.getBackground() + term.setForeground(colors.white) + term.setBackground(colors.gray) + end + if sReplace then + term.write(string.rep(sReplace, #sCompletion)) + else + term.write(sCompletion) + end + if not _bClear then + term.setForeground(oldText) + term.setBackground(oldBg) + end + end + + term.setPos(sx + nPos - nScroll, cy) + end + + local function clear() + redraw(true) + end + + recomplete() + redraw() + + local function acceptCompletion() + if nCompletion then + -- Clear + clear() + + -- Find the common prefix of all the other suggestions which start with the same letter as the current one + local sCompletion = tCompletions[nCompletion] + sLine = sLine .. sCompletion + nPos = #sLine + + -- Redraw + recomplete() + redraw() + end + end + while true do + local sEvent, param, param1, param2 = event.pull() + if sEvent == "char" then + -- Typed key + clear() + sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1) + nPos = nPos + 1 + recomplete() + redraw() + + elseif sEvent == "paste" then + -- Pasted text + clear() + sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1) + nPos = nPos + #param + recomplete() + redraw() + + elseif sEvent == "key_down" then + if param1 == "enter" then + -- Enter/Numpad Enter + if nCompletion then + clear() + uncomplete() + redraw() + end + break + + elseif param1 == "left" then + -- Left + if nPos > 0 then + clear() + nPos = nPos - 1 + recomplete() + redraw() + end + + elseif param1 == "right" then + -- Right + if nPos < #sLine then + -- Move right + clear() + nPos = nPos + 1 + recomplete() + redraw() + else + -- Accept autocomplete + acceptCompletion() + end + + elseif param1 == "up" or param1 == "down" then + -- Up or down + if nCompletion then + -- Cycle completions + clear() + if param == "up" then + nCompletion = nCompletion - 1 + if nCompletion < 1 then + nCompletion = #tCompletions + end + elseif param == "down" then + nCompletion = nCompletion + 1 + if nCompletion > #tCompletions then + nCompletion = 1 + end + end + redraw() + + elseif _tHistory then + -- Cycle history + clear() + if param1 == "up" then + -- Up + if nHistoryPos == nil then + if #_tHistory > 0 then + nHistoryPos = #_tHistory + end + elseif nHistoryPos > 1 then + nHistoryPos = nHistoryPos - 1 + end + else + -- Down + if nHistoryPos == #_tHistory then + nHistoryPos = nil + elseif nHistoryPos ~= nil then + nHistoryPos = nHistoryPos + 1 + end + end + if nHistoryPos then + sLine = _tHistory[nHistoryPos] + nPos, nScroll = #sLine, 0 + else + sLine = "" + nPos, nScroll = 0, 0 + end + uncomplete() + redraw() + + end + + elseif param1 == "back" then + -- Backspace + if nPos > 0 then + clear() + sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1) + nPos = nPos - 1 + if nScroll > 0 then nScroll = nScroll - 1 end + recomplete() + redraw() + end + + elseif param1 == "home" then + -- Home + if nPos > 0 then + clear() + nPos = 0 + recomplete() + redraw() + end + + elseif param1 == "delete" then + -- Delete + if nPos < #sLine then + clear() + sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2) + recomplete() + redraw() + end + + elseif param1 == "end" then + -- End + if nPos < #sLine then + clear() + nPos = #sLine + recomplete() + redraw() + end + + elseif param1 == "tab" then + -- Tab (accept autocomplete) + acceptCompletion() + + end + + elseif sEvent == "mouse_down" or sEvent == "mouse_drag" and param == 1 then + local _, cy = term.getPos() + if param1 >= sx and param1 <= w and param2 == cy then + -- Ensure we don't scroll beyond the current line + nPos = math.min(math.max(nScroll + param1 - sx, 0), #sLine) + redraw() + end + + elseif sEvent == "term_resize" then + -- Terminal resized + w = term.getSize() + redraw() + + end + end + + local _, cy = term.getPos() + term.setBlink(false) + term.setPos(w + 1, cy) + print() + + return sLine +end + + +function printError(...) + local r, g, b = term.getForeground() + term.setForeground(colors.red) + print(...) + term.setForeground(r, g, b) +end + +local tEnv = { + ["_echo"] = function(...) + return ... + end, + p = function(t, sv) + for k, v in pairs(t) do + if sv then + print(k, v) + else + print(k, type(v)) + end + end + end, + fs = require("fs"), +} +setmetatable(tEnv, { __index = _ENV }) + +local tCommandHistory = {} + +term.setSize(51, 19) + +term.setForeground(colors.yellow) +term.setBackground(colors.black) +term.clear() +term.setPos(1,1) +print("Lua prompt") +while true do + term.setForeground(colors.yellow) + write("> ") + term.setForeground(colors.white) + local s = read(nil, tCommandHistory) + if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then + table.insert(tCommandHistory, s) + end + + local nForcePrint = 0 + local func, e = load(s, "=lua", "t", tEnv) + local func2 = load("return _echo(" .. s .. ");", "=lua", "t", tEnv) + + if not func then + if func2 then + func = func2 + e = nil + nForcePrint = 1 + end + else + if func2 then + func = func2 + end + end + + if func then + local tResults = table.pack(pcall(func)) + if tResults[1] then + local n = 1 + while n < tResults.n or n <= nForcePrint do + local value = tResults[n + 1] + print(tostring(value)) + n = n + 1 + end + else + printError(tResults[2]) + end + else + printError(e) + end + +end \ No newline at end of file diff --git a/Capy64/Assets/Lua/lib/colors.lua b/Capy64/Assets/Lua/lib/colors.lua new file mode 100644 index 0000000..5a2aca7 --- /dev/null +++ b/Capy64/Assets/Lua/lib/colors.lua @@ -0,0 +1,93 @@ +local palette = { + { + "white", + 0xf0f0f0 + }, + { + "orange", + 0xf2b233 + }, + { + "magenta", + 0xe57fd8 + }, + { + "lightBlue", + 0x99b2f2 + }, + { + "yellow", + 0xdede6c + }, + { + "lime", + 0x7fcc19 + }, + { + "pink", + 0xf2b2cc + }, + { + "gray", + 0x4c4c4c + }, + { + "lightGray", + 0x999999 + }, + { + "cyan", + 0x4c99b2 + }, + { + "purple", + 0xb266e5 + }, + { + "blue", + 0x3366cc + }, + { + "brown", + 0x7f664c + }, + { + "green", + 0x57a64e + }, + { + "red", + 0xcc4c4c + }, + { + "black", + 0x111111 + } +} + +--[[local colors = { + white = 0xf0f0f0, + orange = 0xf2b233, + magenta = 0xe57fd8, + lightBlue = 0x99b2f2, + yellow = 0xdede6c, + lime = 0x7fcc19, + pink = 0xf2b2cc, + gray = 0x4c4c4c, + lightGray = 0x999999, + cyan = 0x4c99b2, + purple = 0xb266e5, + blue = 0x3366cc, + brown = 0x7f664c, + green = 0x57a64e, + red = 0xcc4c4c, + black = 0x111111, +}]] + +local colors = {} +for k, v in ipairs(palette) do + colors[v[1]] = v[2] + colors[k] = v[2] +end + +return colors; \ No newline at end of file diff --git a/Capy64/Assets/Lua/lib/event.lua b/Capy64/Assets/Lua/lib/event.lua new file mode 100644 index 0000000..b15af13 --- /dev/null +++ b/Capy64/Assets/Lua/lib/event.lua @@ -0,0 +1,15 @@ +local event = {} + +function event.pull(...) + local pars = table.pack(event.pullRaw(...)) + if pars[1] == "interrupt" then + error("Interrupted", 0) + end + return table.unpack(pars) +end + +function event.pullRaw(...) + return coroutine.yield(...) +end + +return event \ No newline at end of file diff --git a/Capy64/Assets/Lua/lib/expect.lua b/Capy64/Assets/Lua/lib/expect.lua new file mode 100644 index 0000000..483f757 --- /dev/null +++ b/Capy64/Assets/Lua/lib/expect.lua @@ -0,0 +1,123 @@ +--[[- The @{cc.expect} library provides helper functions for verifying that +function arguments are well-formed and of the correct type. + +@module cc.expect +@since 1.84.0 +@changed 1.96.0 The module can now be called directly as a function, which wraps around `expect.expect`. +@usage Define a basic function and check it has the correct arguments. + + local expect = require "cc.expect" + local expect, field = expect.expect, expect.field + + local function add_person(name, info) + expect(1, name, "string") + expect(2, info, "table", "nil") + + if info then + print("Got age=", field(info, "age", "number")) + print("Got gender=", field(info, "gender", "string", "nil")) + end + end + + add_person("Anastazja") -- `info' is optional + add_person("Kion", { age = 23 }) -- `gender' is optional + add_person("Caoimhin", { age = 23, gender = true }) -- error! +]] + +local native_select, native_type = select, type + +local function get_type_names(...) + local types = table.pack(...) + for i = types.n, 1, -1 do + if types[i] == "nil" then table.remove(types, i) end + end + + if #types <= 1 then + return tostring(...) + else + return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types] + end +end + +--- Expect an argument to have a specific type. +-- +-- @tparam number index The 1-based argument index. +-- @param value The argument's value. +-- @tparam string ... The allowed types of the argument. +-- @return The given `value`. +-- @throws If the value is not one of the allowed types. +local function expect(index, value, ...) + local t = native_type(value) + for i = 1, native_select("#", ...) do + if t == native_select(i, ...) then return value end + end + + -- If we can determine the function name with a high level of confidence, try to include it. + local name + local ok, info = pcall(debug.getinfo, 3, "nS") + if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end + + local type_names = get_type_names(...) + if name then + error(("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3) + else + error(("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3) + end +end + +--- Expect an field to have a specific type. +-- +-- @tparam table tbl The table to index. +-- @tparam string index The field name to check. +-- @tparam string ... The allowed types of the argument. +-- @return The contents of the given field. +-- @throws If the field is not one of the allowed types. +local function field(tbl, index, ...) + expect(1, tbl, "table") + expect(2, index, "string") + + local value = tbl[index] + local t = native_type(value) + for i = 1, native_select("#", ...) do + if t == native_select(i, ...) then return value end + end + + if value == nil then + error(("field '%s' missing from table"):format(index), 3) + else + error(("bad field '%s' (expected %s, got %s)"):format(index, get_type_names(...), t), 3) + end +end + +local function is_nan(num) + return num ~= num +end + +--- Expect a number to be within a specific range. +-- +-- @tparam number num The value to check. +-- @tparam number min The minimum value, if nil then `-math.huge` is used. +-- @tparam number max The maximum value, if nil then `math.huge` is used. +-- @return The given `value`. +-- @throws If the value is outside of the allowed range. +-- @since 1.96.0 +local function range(num, min, max) + expect(1, num, "number") + min = expect(2, min, "number", "nil") or -math.huge + max = expect(3, max, "number", "nil") or math.huge + if min > max then + error("min must be less than or equal to max)", 2) + end + + if is_nan(num) or num < min or num > max then + error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3) + end + + return num +end + +return setmetatable({ + expect = expect, + field = field, + range = range, +}, { __call = function(_, ...) return expect(...) end }) diff --git a/Capy64/Assets/Lua/lib/utfstring.lua b/Capy64/Assets/Lua/lib/utfstring.lua new file mode 100644 index 0000000..7119373 --- /dev/null +++ b/Capy64/Assets/Lua/lib/utfstring.lua @@ -0,0 +1,1762 @@ +local expect = require "expect" + +local UTFString = {} +local UTFString_mt = {__index = UTFString, __name = "UTFString"} + +local function toUTF8(s) + -- convert from ANSI if the string is invalid in UTF-8 + local ss = "" + local iter, invar, i = utf8.codes(s) + local ok, c + while i do + local oi = i + ok, i, c = pcall(iter, invar, i) + if not ok then i, ss = oi + 1, ss .. utf8.char(string.byte(s, utf8.offset(s, oi) + 1)) + elseif c then ss = ss .. utf8.char(c) end + end + return ss +end + +function UTFString:new(s) + return setmetatable({str = toUTF8(s)}, UTFString_mt) +end + +setmetatable(UTFString, {__call = UTFString.new}) + +local utf8_case_conv_utl, utf8_case_conv_ltu = nil, {} -- defined at bottom of file + +-- string library implementation + +function UTFString:byte(i, j) + expect(1, i, "number", "nil") + expect(2, j, "number", "nil") + return utf8.codepoint(self.str, i, j) +end + +UTFString.char = utf8.char +UTFString.dump = string.dump + +function UTFString:find(pattern, init, plain) + expect(1, pattern, "string", "table") + expect(2, init, "number", "nil") + if type(pattern) == "table" then + if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end + pattern = pattern.str + else pattern = toUTF8(pattern) end + pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern) + local s, e = string.find(self.str, pattern, init and utf8.offset(self.str, init), plain) + if not s then return nil end + return utf8.len(string.sub(self.str, 1, s - 1)) + 1, utf8.len(string.sub(self.str, 1, e - 1)) + 1 +end + +function UTFString:format(...) + local args = table.pack(...) + for i = 1, args.n do + if type(args[i]) == "table" and getmetatable(args[i]) == UTFString_mt then args[i] = args[i].str + elseif type(args[i]) == "string" then args[i] = toUTF8(args[i]) end + end + return UTFString(string.format(self.str, table.unpack(args, 1, args.n))) +end + +function UTFString:gmatch(pattern) + expect(1, pattern, "string", "table") + if type(pattern) == "table" then + if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end + pattern = pattern.str + else pattern = toUTF8(pattern) end + pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern) + local iter = string.gmatch(self.str, pattern) + return function() + local matches = table.pack(iter()) + for i = 1, matches.n do + local tt = type(matches[i]) + if tt == "string" then matches[i] = UTFString(matches[i]) + elseif tt == "number" then matches[i] = utf8.len(string.sub(self.str, 1, matches[i])) + 1 end + end + return table.unpack(matches, 1, matches.n) + end +end + +function UTFString:gsub(pattern, repl, n) + expect(1, pattern, "string", "table") + expect(2, repl, "string", "table", "function") + expect(3, n, "number", "nil") + if type(pattern) == "table" then + if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end + pattern = pattern.str + else pattern = toUTF8(pattern)end + if type(repl) == "table" and getmetatable(repl) == UTFString_mt then repl = repl.str + elseif type(repl) == "string" then repl = toUTF8(repl) end + pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern) + local s, total = string.gsub(self.str, pattern, repl, n) + return UTFString(s), total +end + +function UTFString:len() + return utf8.len(self.str) +end + +function UTFString:lower() + local s = "" + for _, c in utf8.codes(self.str) do + if utf8_case_conv_utl[c] then + if type(utf8_case_conv_utl[c]) == "table" then s = s .. utf8.char(table.unpack(utf8_case_conv_utl[c])) + else s = s .. utf8.char(utf8_case_conv_utl[c]) end + else s = s .. utf8.char(c) end + end + return UTFString(s) +end + +function UTFString:match(pattern, init) + expect(1, pattern, "string", "table") + expect(2, init, "number", "nil") + if type(pattern) == "table" then + if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end + pattern = pattern.str + else pattern = toUTF8(pattern) end + pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern) + local matches = table.pack(string.match(self.str, pattern, init and utf8.offset(self.str, init))) + for i = 1, matches.n do + local tt = type(matches[i]) + if tt == "string" then matches[i] = UTFString(matches[i]) + elseif tt == "number" then matches[i] = utf8.len(string.sub(self.str, 1, matches[i])) + 1 end + end + return table.unpack(matches, 1, matches.n) +end + +function UTFString:pack(...) + return UTFString(string.pack(self.str, ...)) +end + +function UTFString:packsize() + return string.packsize(self.str) +end + +function UTFString:rep(n, sep) + expect(1, n, "number") + expect(2, sep, "string", "table", "nil") + if type(sep) == "table" then + if getmetatable(sep) ~= UTFString_mt then error("bad argument #2 (expected string or UTFString, got table)", 2) end + sep = sep.str + elseif sep then sep = toUTF8(sep) end + return UTFString(string.rep(self.str, n, sep)) +end + +function UTFString:reverse() + local codes = {n = 0} + for _, c in utf8.codes(self.str) do + codes.n = codes.n + 1 + codes[codes.n] = c + end + local s = "" + for i = codes.n, 1, -1 do s = s .. utf8.char(codes[i]) end + return UTFString(s) +end + +function UTFString:sub(i, j) + expect(1, i, "number") + expect(2, j, "number", "nil") + return UTFString(string.sub(self.str, utf8.offset(self.str, i), j and utf8.offset(self.str, j))) +end + +function UTFString:unpack(s, pos) + expect(1, s, "string", "table") + expect(2, pos, "number", "nil") + if type(s) == "table" then + if getmetatable(s) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end + s = s.str + if pos then pos = utf8.offset(s, pos) end + end + return string.unpack(self.str, s, pos) +end + +function UTFString:upper() + local s = "" + for _, c in utf8.codes(self.str) do + if utf8_case_conv_ltu[c] then + if type(utf8_case_conv_ltu[c]) == "table" then s = s .. utf8.char(table.unpack(utf8_case_conv_ltu[c])) + else s = s .. utf8.char(utf8_case_conv_ltu[c]) end + else s = s .. utf8.char(c) end + end + return UTFString(s) +end + +-- metamethods + +function UTFString_mt:__len() + return utf8.len(self.str) +end + +function UTFString_mt.__concat(a, b) + if type(a) == "string" then return UTFString(a .. b.str) + elseif type(b) == "string" then return UTFString(a.str .. b) + else + if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to concatenate " .. type(a) .. " and UTFString", 2) + elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to concatenate UTFString and " .. type(b), 2) end + return UTFString(a.str .. b.str) + end +end + +function UTFString_mt.__eq(a, b) + if type(a) == "string" then return a == b.str + elseif type(b) == "string" then return a.str == b + elseif type(a) ~= "table" or type(b) ~= "table" or getmetatable(a) ~= UTFString_mt or getmetatable(b) ~= UTFString_mt then return false + else return a.str == b.str end +end + +function UTFString_mt.__lt(a, b) + if type(a) == "string" then return a < b.str + elseif type(b) == "string" then return a.str < b + else + if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " .. type(a) .. " and UTFString", 2) + elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " .. type(b), 2) end + return a.str < b.str + end +end + +function UTFString_mt.__le(a, b) + if type(a) == "string" then return a <= b.str + elseif type(b) == "string" then return a.str <= b + else + if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " .. type(a) .. " and UTFString", 2) + elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " .. type(b), 2) end + return a.str <= b.str + end +end + +function UTFString_mt:__tostring() + local str = "" + for _, c in utf8.codes(self.str) do + if c > 255 then str = str .. "?" + else str = str .. string.char(c) end + end + return str +end + +-- tables for UTF-8 case conversion + +utf8_case_conv_utl = { + [0x0041] = 0x0061, + [0x0042] = 0x0062, + [0x0043] = 0x0063, + [0x0044] = 0x0064, + [0x0045] = 0x0065, + [0x0046] = 0x0066, + [0x0047] = 0x0067, + [0x0048] = 0x0068, + [0x0049] = 0x0069, + + [0x004A] = 0x006A, + [0x004B] = 0x006B, + [0x004C] = 0x006C, + [0x004D] = 0x006D, + [0x004E] = 0x006E, + [0x004F] = 0x006F, + [0x0050] = 0x0070, + [0x0051] = 0x0071, + [0x0052] = 0x0072, + [0x0053] = 0x0073, + [0x0054] = 0x0074, + [0x0055] = 0x0075, + [0x0056] = 0x0076, + [0x0057] = 0x0077, + [0x0058] = 0x0078, + [0x0059] = 0x0079, + [0x005A] = 0x007A, + [0x00B5] = 0x03BC, + [0x00C0] = 0x00E0, + [0x00C1] = 0x00E1, + [0x00C2] = 0x00E2, + [0x00C3] = 0x00E3, + [0x00C4] = 0x00E4, + [0x00C5] = 0x00E5, + [0x00C6] = 0x00E6, + [0x00C7] = 0x00E7, + [0x00C8] = 0x00E8, + [0x00C9] = 0x00E9, + [0x00CA] = 0x00EA, + [0x00CB] = 0x00EB, + [0x00CC] = 0x00EC, + [0x00CD] = 0x00ED, + [0x00CE] = 0x00EE, + [0x00CF] = 0x00EF, + [0x00D0] = 0x00F0, + [0x00D1] = 0x00F1, + [0x00D2] = 0x00F2, + [0x00D3] = 0x00F3, + [0x00D4] = 0x00F4, + [0x00D5] = 0x00F5, + [0x00D6] = 0x00F6, + [0x00D8] = 0x00F8, + [0x00D9] = 0x00F9, + [0x00DA] = 0x00FA, + [0x00DB] = 0x00FB, + [0x00DC] = 0x00FC, + [0x00DD] = 0x00FD, + [0x00DE] = 0x00FE, + [0x00DF] = {0x0073, 0x0073}, + [0x0100] = 0x0101, + [0x0102] = 0x0103, + [0x0104] = 0x0105, + [0x0106] = 0x0107, + [0x0108] = 0x0109, + [0x010A] = 0x010B, + [0x010C] = 0x010D, + [0x010E] = 0x010F, + [0x0110] = 0x0111, + [0x0112] = 0x0113, + [0x0114] = 0x0115, + [0x0116] = 0x0117, + [0x0118] = 0x0119, + [0x011A] = 0x011B, + [0x011C] = 0x011D, + [0x011E] = 0x011F, + [0x0120] = 0x0121, + [0x0122] = 0x0123, + [0x0124] = 0x0125, + [0x0126] = 0x0127, + [0x0128] = 0x0129, + [0x012A] = 0x012B, + [0x012C] = 0x012D, + [0x012E] = 0x012F, + [0x0130] = {0x0069, 0x0307}, + + [0x0132] = 0x0133, + [0x0134] = 0x0135, + [0x0136] = 0x0137, + [0x0139] = 0x013A, + [0x013B] = 0x013C, + [0x013D] = 0x013E, + [0x013F] = 0x0140, + [0x0141] = 0x0142, + [0x0143] = 0x0144, + [0x0145] = 0x0146, + [0x0147] = 0x0148, + [0x0149] = {0x02BC, 0x006E}, + [0x014A] = 0x014B, + [0x014C] = 0x014D, + [0x014E] = 0x014F, + [0x0150] = 0x0151, + [0x0152] = 0x0153, + [0x0154] = 0x0155, + [0x0156] = 0x0157, + [0x0158] = 0x0159, + [0x015A] = 0x015B, + [0x015C] = 0x015D, + [0x015E] = 0x015F, + [0x0160] = 0x0161, + [0x0162] = 0x0163, + [0x0164] = 0x0165, + [0x0166] = 0x0167, + [0x0168] = 0x0169, + [0x016A] = 0x016B, + [0x016C] = 0x016D, + [0x016E] = 0x016F, + [0x0170] = 0x0171, + [0x0172] = 0x0173, + [0x0174] = 0x0175, + [0x0176] = 0x0177, + [0x0178] = 0x00FF, + [0x0179] = 0x017A, + [0x017B] = 0x017C, + [0x017D] = 0x017E, + [0x017F] = 0x0073, + [0x0181] = 0x0253, + [0x0182] = 0x0183, + [0x0184] = 0x0185, + [0x0186] = 0x0254, + [0x0187] = 0x0188, + [0x0189] = 0x0256, + [0x018A] = 0x0257, + [0x018B] = 0x018C, + [0x018E] = 0x01DD, + [0x018F] = 0x0259, + [0x0190] = 0x025B, + [0x0191] = 0x0192, + [0x0193] = 0x0260, + [0x0194] = 0x0263, + [0x0196] = 0x0269, + [0x0197] = 0x0268, + [0x0198] = 0x0199, + [0x019C] = 0x026F, + [0x019D] = 0x0272, + [0x019F] = 0x0275, + [0x01A0] = 0x01A1, + [0x01A2] = 0x01A3, + [0x01A4] = 0x01A5, + [0x01A6] = 0x0280, + [0x01A7] = 0x01A8, + [0x01A9] = 0x0283, + [0x01AC] = 0x01AD, + [0x01AE] = 0x0288, + [0x01AF] = 0x01B0, + [0x01B1] = 0x028A, + [0x01B2] = 0x028B, + [0x01B3] = 0x01B4, + [0x01B5] = 0x01B6, + [0x01B7] = 0x0292, + [0x01B8] = 0x01B9, + [0x01BC] = 0x01BD, + [0x01C4] = 0x01C6, + [0x01C5] = 0x01C6, + [0x01C7] = 0x01C9, + [0x01C8] = 0x01C9, + [0x01CA] = 0x01CC, + [0x01CB] = 0x01CC, + [0x01CD] = 0x01CE, + [0x01CF] = 0x01D0, + [0x01D1] = 0x01D2, + [0x01D3] = 0x01D4, + [0x01D5] = 0x01D6, + [0x01D7] = 0x01D8, + [0x01D9] = 0x01DA, + [0x01DB] = 0x01DC, + [0x01DE] = 0x01DF, + [0x01E0] = 0x01E1, + [0x01E2] = 0x01E3, + [0x01E4] = 0x01E5, + [0x01E6] = 0x01E7, + [0x01E8] = 0x01E9, + [0x01EA] = 0x01EB, + [0x01EC] = 0x01ED, + [0x01EE] = 0x01EF, + [0x01F0] = {0x006A, 0x030C}, + [0x01F1] = 0x01F3, + [0x01F2] = 0x01F3, + [0x01F4] = 0x01F5, + [0x01F6] = 0x0195, + [0x01F7] = 0x01BF, + [0x01F8] = 0x01F9, + [0x01FA] = 0x01FB, + [0x01FC] = 0x01FD, + [0x01FE] = 0x01FF, + [0x0200] = 0x0201, + [0x0202] = 0x0203, + [0x0204] = 0x0205, + [0x0206] = 0x0207, + [0x0208] = 0x0209, + [0x020A] = 0x020B, + [0x020C] = 0x020D, + [0x020E] = 0x020F, + [0x0210] = 0x0211, + [0x0212] = 0x0213, + [0x0214] = 0x0215, + [0x0216] = 0x0217, + [0x0218] = 0x0219, + [0x021A] = 0x021B, + [0x021C] = 0x021D, + [0x021E] = 0x021F, + [0x0220] = 0x019E, + [0x0222] = 0x0223, + [0x0224] = 0x0225, + [0x0226] = 0x0227, + [0x0228] = 0x0229, + [0x022A] = 0x022B, + [0x022C] = 0x022D, + [0x022E] = 0x022F, + [0x0230] = 0x0231, + [0x0232] = 0x0233, + [0x023A] = 0x2C65, + [0x023B] = 0x023C, + [0x023D] = 0x019A, + [0x023E] = 0x2C66, + [0x0241] = 0x0242, + [0x0243] = 0x0180, + [0x0244] = 0x0289, + [0x0245] = 0x028C, + [0x0246] = 0x0247, + [0x0248] = 0x0249, + [0x024A] = 0x024B, + [0x024C] = 0x024D, + [0x024E] = 0x024F, + [0x0345] = 0x03B9, + [0x0370] = 0x0371, + [0x0372] = 0x0373, + [0x0376] = 0x0377, + [0x037F] = 0x03F3, + [0x0386] = 0x03AC, + [0x0388] = 0x03AD, + [0x0389] = 0x03AE, + [0x038A] = 0x03AF, + [0x038C] = 0x03CC, + [0x038E] = 0x03CD, + [0x038F] = 0x03CE, + [0x0390] = {0x03B9, 0x0308, 0x0301}, + [0x0391] = 0x03B1, + [0x0392] = 0x03B2, + [0x0393] = 0x03B3, + [0x0394] = 0x03B4, + [0x0395] = 0x03B5, + [0x0396] = 0x03B6, + [0x0397] = 0x03B7, + [0x0398] = 0x03B8, + [0x0399] = 0x03B9, + [0x039A] = 0x03BA, + [0x039B] = 0x03BB, + [0x039C] = 0x03BC, + [0x039D] = 0x03BD, + [0x039E] = 0x03BE, + [0x039F] = 0x03BF, + [0x03A0] = 0x03C0, + [0x03A1] = 0x03C1, + [0x03A3] = 0x03C3, + [0x03A4] = 0x03C4, + [0x03A5] = 0x03C5, + [0x03A6] = 0x03C6, + [0x03A7] = 0x03C7, + [0x03A8] = 0x03C8, + [0x03A9] = 0x03C9, + [0x03AA] = 0x03CA, + [0x03AB] = 0x03CB, + [0x03B0] = {0x03C5, 0x0308, 0x0301}, + [0x03C2] = 0x03C3, + [0x03CF] = 0x03D7, + [0x03D0] = 0x03B2, + [0x03D1] = 0x03B8, + [0x03D5] = 0x03C6, + [0x03D6] = 0x03C0, + [0x03D8] = 0x03D9, + [0x03DA] = 0x03DB, + [0x03DC] = 0x03DD, + [0x03DE] = 0x03DF, + [0x03E0] = 0x03E1, + [0x03E2] = 0x03E3, + [0x03E4] = 0x03E5, + [0x03E6] = 0x03E7, + [0x03E8] = 0x03E9, + [0x03EA] = 0x03EB, + [0x03EC] = 0x03ED, + [0x03EE] = 0x03EF, + [0x03F0] = 0x03BA, + [0x03F1] = 0x03C1, + [0x03F4] = 0x03B8, + [0x03F5] = 0x03B5, + [0x03F7] = 0x03F8, + [0x03F9] = 0x03F2, + [0x03FA] = 0x03FB, + [0x03FD] = 0x037B, + [0x03FE] = 0x037C, + [0x03FF] = 0x037D, + [0x0400] = 0x0450, + [0x0401] = 0x0451, + [0x0402] = 0x0452, + [0x0403] = 0x0453, + [0x0404] = 0x0454, + [0x0405] = 0x0455, + [0x0406] = 0x0456, + [0x0407] = 0x0457, + [0x0408] = 0x0458, + [0x0409] = 0x0459, + [0x040A] = 0x045A, + [0x040B] = 0x045B, + [0x040C] = 0x045C, + [0x040D] = 0x045D, + [0x040E] = 0x045E, + [0x040F] = 0x045F, + [0x0410] = 0x0430, + [0x0411] = 0x0431, + [0x0412] = 0x0432, + [0x0413] = 0x0433, + [0x0414] = 0x0434, + [0x0415] = 0x0435, + [0x0416] = 0x0436, + [0x0417] = 0x0437, + [0x0418] = 0x0438, + [0x0419] = 0x0439, + [0x041A] = 0x043A, + [0x041B] = 0x043B, + [0x041C] = 0x043C, + [0x041D] = 0x043D, + [0x041E] = 0x043E, + [0x041F] = 0x043F, + [0x0420] = 0x0440, + [0x0421] = 0x0441, + [0x0422] = 0x0442, + [0x0423] = 0x0443, + [0x0424] = 0x0444, + [0x0425] = 0x0445, + [0x0426] = 0x0446, + [0x0427] = 0x0447, + [0x0428] = 0x0448, + [0x0429] = 0x0449, + [0x042A] = 0x044A, + [0x042B] = 0x044B, + [0x042C] = 0x044C, + [0x042D] = 0x044D, + [0x042E] = 0x044E, + [0x042F] = 0x044F, + [0x0460] = 0x0461, + [0x0462] = 0x0463, + [0x0464] = 0x0465, + [0x0466] = 0x0467, + [0x0468] = 0x0469, + [0x046A] = 0x046B, + [0x046C] = 0x046D, + [0x046E] = 0x046F, + [0x0470] = 0x0471, + [0x0472] = 0x0473, + [0x0474] = 0x0475, + [0x0476] = 0x0477, + [0x0478] = 0x0479, + [0x047A] = 0x047B, + [0x047C] = 0x047D, + [0x047E] = 0x047F, + [0x0480] = 0x0481, + [0x048A] = 0x048B, + [0x048C] = 0x048D, + [0x048E] = 0x048F, + [0x0490] = 0x0491, + [0x0492] = 0x0493, + [0x0494] = 0x0495, + [0x0496] = 0x0497, + [0x0498] = 0x0499, + [0x049A] = 0x049B, + [0x049C] = 0x049D, + [0x049E] = 0x049F, + [0x04A0] = 0x04A1, + [0x04A2] = 0x04A3, + [0x04A4] = 0x04A5, + [0x04A6] = 0x04A7, + [0x04A8] = 0x04A9, + [0x04AA] = 0x04AB, + [0x04AC] = 0x04AD, + [0x04AE] = 0x04AF, + [0x04B0] = 0x04B1, + [0x04B2] = 0x04B3, + [0x04B4] = 0x04B5, + [0x04B6] = 0x04B7, + [0x04B8] = 0x04B9, + [0x04BA] = 0x04BB, + [0x04BC] = 0x04BD, + [0x04BE] = 0x04BF, + [0x04C0] = 0x04CF, + [0x04C1] = 0x04C2, + [0x04C3] = 0x04C4, + [0x04C5] = 0x04C6, + [0x04C7] = 0x04C8, + [0x04C9] = 0x04CA, + [0x04CB] = 0x04CC, + [0x04CD] = 0x04CE, + [0x04D0] = 0x04D1, + [0x04D2] = 0x04D3, + [0x04D4] = 0x04D5, + [0x04D6] = 0x04D7, + [0x04D8] = 0x04D9, + [0x04DA] = 0x04DB, + [0x04DC] = 0x04DD, + [0x04DE] = 0x04DF, + [0x04E0] = 0x04E1, + [0x04E2] = 0x04E3, + [0x04E4] = 0x04E5, + [0x04E6] = 0x04E7, + [0x04E8] = 0x04E9, + [0x04EA] = 0x04EB, + [0x04EC] = 0x04ED, + [0x04EE] = 0x04EF, + [0x04F0] = 0x04F1, + [0x04F2] = 0x04F3, + [0x04F4] = 0x04F5, + [0x04F6] = 0x04F7, + [0x04F8] = 0x04F9, + [0x04FA] = 0x04FB, + [0x04FC] = 0x04FD, + [0x04FE] = 0x04FF, + [0x0500] = 0x0501, + [0x0502] = 0x0503, + [0x0504] = 0x0505, + [0x0506] = 0x0507, + [0x0508] = 0x0509, + [0x050A] = 0x050B, + [0x050C] = 0x050D, + [0x050E] = 0x050F, + [0x0510] = 0x0511, + [0x0512] = 0x0513, + [0x0514] = 0x0515, + [0x0516] = 0x0517, + [0x0518] = 0x0519, + [0x051A] = 0x051B, + [0x051C] = 0x051D, + [0x051E] = 0x051F, + [0x0520] = 0x0521, + [0x0522] = 0x0523, + [0x0524] = 0x0525, + [0x0526] = 0x0527, + [0x0528] = 0x0529, + [0x052A] = 0x052B, + [0x052C] = 0x052D, + [0x052E] = 0x052F, + [0x0531] = 0x0561, + [0x0532] = 0x0562, + [0x0533] = 0x0563, + [0x0534] = 0x0564, + [0x0535] = 0x0565, + [0x0536] = 0x0566, + [0x0537] = 0x0567, + [0x0538] = 0x0568, + [0x0539] = 0x0569, + [0x053A] = 0x056A, + [0x053B] = 0x056B, + [0x053C] = 0x056C, + [0x053D] = 0x056D, + [0x053E] = 0x056E, + [0x053F] = 0x056F, + [0x0540] = 0x0570, + [0x0541] = 0x0571, + [0x0542] = 0x0572, + [0x0543] = 0x0573, + [0x0544] = 0x0574, + [0x0545] = 0x0575, + [0x0546] = 0x0576, + [0x0547] = 0x0577, + [0x0548] = 0x0578, + [0x0549] = 0x0579, + [0x054A] = 0x057A, + [0x054B] = 0x057B, + [0x054C] = 0x057C, + [0x054D] = 0x057D, + [0x054E] = 0x057E, + [0x054F] = 0x057F, + [0x0550] = 0x0580, + [0x0551] = 0x0581, + [0x0552] = 0x0582, + [0x0553] = 0x0583, + [0x0554] = 0x0584, + [0x0555] = 0x0585, + [0x0556] = 0x0586, + [0x0587] = {0x0565, 0x0582}, + [0x10A0] = 0x2D00, + [0x10A1] = 0x2D01, + [0x10A2] = 0x2D02, + [0x10A3] = 0x2D03, + [0x10A4] = 0x2D04, + [0x10A5] = 0x2D05, + [0x10A6] = 0x2D06, + [0x10A7] = 0x2D07, + [0x10A8] = 0x2D08, + [0x10A9] = 0x2D09, + [0x10AA] = 0x2D0A, + [0x10AB] = 0x2D0B, + [0x10AC] = 0x2D0C, + [0x10AD] = 0x2D0D, + [0x10AE] = 0x2D0E, + [0x10AF] = 0x2D0F, + [0x10B0] = 0x2D10, + [0x10B1] = 0x2D11, + [0x10B2] = 0x2D12, + [0x10B3] = 0x2D13, + [0x10B4] = 0x2D14, + [0x10B5] = 0x2D15, + [0x10B6] = 0x2D16, + [0x10B7] = 0x2D17, + [0x10B8] = 0x2D18, + [0x10B9] = 0x2D19, + [0x10BA] = 0x2D1A, + [0x10BB] = 0x2D1B, + [0x10BC] = 0x2D1C, + [0x10BD] = 0x2D1D, + [0x10BE] = 0x2D1E, + [0x10BF] = 0x2D1F, + [0x10C0] = 0x2D20, + [0x10C1] = 0x2D21, + [0x10C2] = 0x2D22, + [0x10C3] = 0x2D23, + [0x10C4] = 0x2D24, + [0x10C5] = 0x2D25, + [0x10C7] = 0x2D27, + [0x10CD] = 0x2D2D, + [0x13F8] = 0x13F0, + [0x13F9] = 0x13F1, + [0x13FA] = 0x13F2, + [0x13FB] = 0x13F3, + [0x13FC] = 0x13F4, + [0x13FD] = 0x13F5, + [0x1C80] = 0x0432, + [0x1C81] = 0x0434, + [0x1C82] = 0x043E, + [0x1C83] = 0x0441, + [0x1C84] = 0x0442, + [0x1C85] = 0x0442, + [0x1C86] = 0x044A, + [0x1C87] = 0x0463, + [0x1C88] = 0xA64B, + [0x1C90] = 0x10D0, + [0x1C91] = 0x10D1, + [0x1C92] = 0x10D2, + [0x1C93] = 0x10D3, + [0x1C94] = 0x10D4, + [0x1C95] = 0x10D5, + [0x1C96] = 0x10D6, + [0x1C97] = 0x10D7, + [0x1C98] = 0x10D8, + [0x1C99] = 0x10D9, + [0x1C9A] = 0x10DA, + [0x1C9B] = 0x10DB, + [0x1C9C] = 0x10DC, + [0x1C9D] = 0x10DD, + [0x1C9E] = 0x10DE, + [0x1C9F] = 0x10DF, + [0x1CA0] = 0x10E0, + [0x1CA1] = 0x10E1, + [0x1CA2] = 0x10E2, + [0x1CA3] = 0x10E3, + [0x1CA4] = 0x10E4, + [0x1CA5] = 0x10E5, + [0x1CA6] = 0x10E6, + [0x1CA7] = 0x10E7, + [0x1CA8] = 0x10E8, + [0x1CA9] = 0x10E9, + [0x1CAA] = 0x10EA, + [0x1CAB] = 0x10EB, + [0x1CAC] = 0x10EC, + [0x1CAD] = 0x10ED, + [0x1CAE] = 0x10EE, + [0x1CAF] = 0x10EF, + [0x1CB0] = 0x10F0, + [0x1CB1] = 0x10F1, + [0x1CB2] = 0x10F2, + [0x1CB3] = 0x10F3, + [0x1CB4] = 0x10F4, + [0x1CB5] = 0x10F5, + [0x1CB6] = 0x10F6, + [0x1CB7] = 0x10F7, + [0x1CB8] = 0x10F8, + [0x1CB9] = 0x10F9, + [0x1CBA] = 0x10FA, + [0x1CBD] = 0x10FD, + [0x1CBE] = 0x10FE, + [0x1CBF] = 0x10FF, + [0x1E00] = 0x1E01, + [0x1E02] = 0x1E03, + [0x1E04] = 0x1E05, + [0x1E06] = 0x1E07, + [0x1E08] = 0x1E09, + [0x1E0A] = 0x1E0B, + [0x1E0C] = 0x1E0D, + [0x1E0E] = 0x1E0F, + [0x1E10] = 0x1E11, + [0x1E12] = 0x1E13, + [0x1E14] = 0x1E15, + [0x1E16] = 0x1E17, + [0x1E18] = 0x1E19, + [0x1E1A] = 0x1E1B, + [0x1E1C] = 0x1E1D, + [0x1E1E] = 0x1E1F, + [0x1E20] = 0x1E21, + [0x1E22] = 0x1E23, + [0x1E24] = 0x1E25, + [0x1E26] = 0x1E27, + [0x1E28] = 0x1E29, + [0x1E2A] = 0x1E2B, + [0x1E2C] = 0x1E2D, + [0x1E2E] = 0x1E2F, + [0x1E30] = 0x1E31, + [0x1E32] = 0x1E33, + [0x1E34] = 0x1E35, + [0x1E36] = 0x1E37, + [0x1E38] = 0x1E39, + [0x1E3A] = 0x1E3B, + [0x1E3C] = 0x1E3D, + [0x1E3E] = 0x1E3F, + [0x1E40] = 0x1E41, + [0x1E42] = 0x1E43, + [0x1E44] = 0x1E45, + [0x1E46] = 0x1E47, + [0x1E48] = 0x1E49, + [0x1E4A] = 0x1E4B, + [0x1E4C] = 0x1E4D, + [0x1E4E] = 0x1E4F, + [0x1E50] = 0x1E51, + [0x1E52] = 0x1E53, + [0x1E54] = 0x1E55, + [0x1E56] = 0x1E57, + [0x1E58] = 0x1E59, + [0x1E5A] = 0x1E5B, + [0x1E5C] = 0x1E5D, + [0x1E5E] = 0x1E5F, + [0x1E60] = 0x1E61, + [0x1E62] = 0x1E63, + [0x1E64] = 0x1E65, + [0x1E66] = 0x1E67, + [0x1E68] = 0x1E69, + [0x1E6A] = 0x1E6B, + [0x1E6C] = 0x1E6D, + [0x1E6E] = 0x1E6F, + [0x1E70] = 0x1E71, + [0x1E72] = 0x1E73, + [0x1E74] = 0x1E75, + [0x1E76] = 0x1E77, + [0x1E78] = 0x1E79, + [0x1E7A] = 0x1E7B, + [0x1E7C] = 0x1E7D, + [0x1E7E] = 0x1E7F, + [0x1E80] = 0x1E81, + [0x1E82] = 0x1E83, + [0x1E84] = 0x1E85, + [0x1E86] = 0x1E87, + [0x1E88] = 0x1E89, + [0x1E8A] = 0x1E8B, + [0x1E8C] = 0x1E8D, + [0x1E8E] = 0x1E8F, + [0x1E90] = 0x1E91, + [0x1E92] = 0x1E93, + [0x1E94] = 0x1E95, + [0x1E96] = {0x0068, 0x0331}, + [0x1E97] = {0x0074, 0x0308}, + [0x1E98] = {0x0077, 0x030A}, + [0x1E99] = {0x0079, 0x030A}, + [0x1E9A] = {0x0061, 0x02BE}, + [0x1E9B] = 0x1E61, + [0x1E9E] = {0x0073, 0x0073}, + --1E9E; S;0x00DF, ; # LATIN CAPITAL LETTER SHARP S + [0x1EA0] = 0x1EA1, + [0x1EA2] = 0x1EA3, + [0x1EA4] = 0x1EA5, + [0x1EA6] = 0x1EA7, + [0x1EA8] = 0x1EA9, + [0x1EAA] = 0x1EAB, + [0x1EAC] = 0x1EAD, + [0x1EAE] = 0x1EAF, + [0x1EB0] = 0x1EB1, + [0x1EB2] = 0x1EB3, + [0x1EB4] = 0x1EB5, + [0x1EB6] = 0x1EB7, + [0x1EB8] = 0x1EB9, + [0x1EBA] = 0x1EBB, + [0x1EBC] = 0x1EBD, + [0x1EBE] = 0x1EBF, + [0x1EC0] = 0x1EC1, + [0x1EC2] = 0x1EC3, + [0x1EC4] = 0x1EC5, + [0x1EC6] = 0x1EC7, + [0x1EC8] = 0x1EC9, + [0x1ECA] = 0x1ECB, + [0x1ECC] = 0x1ECD, + [0x1ECE] = 0x1ECF, + [0x1ED0] = 0x1ED1, + [0x1ED2] = 0x1ED3, + [0x1ED4] = 0x1ED5, + [0x1ED6] = 0x1ED7, + [0x1ED8] = 0x1ED9, + [0x1EDA] = 0x1EDB, + [0x1EDC] = 0x1EDD, + [0x1EDE] = 0x1EDF, + [0x1EE0] = 0x1EE1, + [0x1EE2] = 0x1EE3, + [0x1EE4] = 0x1EE5, + [0x1EE6] = 0x1EE7, + [0x1EE8] = 0x1EE9, + [0x1EEA] = 0x1EEB, + [0x1EEC] = 0x1EED, + [0x1EEE] = 0x1EEF, + [0x1EF0] = 0x1EF1, + [0x1EF2] = 0x1EF3, + [0x1EF4] = 0x1EF5, + [0x1EF6] = 0x1EF7, + [0x1EF8] = 0x1EF9, + [0x1EFA] = 0x1EFB, + [0x1EFC] = 0x1EFD, + [0x1EFE] = 0x1EFF, + [0x1F08] = 0x1F00, + [0x1F09] = 0x1F01, + [0x1F0A] = 0x1F02, + [0x1F0B] = 0x1F03, + [0x1F0C] = 0x1F04, + [0x1F0D] = 0x1F05, + [0x1F0E] = 0x1F06, + [0x1F0F] = 0x1F07, + [0x1F18] = 0x1F10, + [0x1F19] = 0x1F11, + [0x1F1A] = 0x1F12, + [0x1F1B] = 0x1F13, + [0x1F1C] = 0x1F14, + [0x1F1D] = 0x1F15, + [0x1F28] = 0x1F20, + [0x1F29] = 0x1F21, + [0x1F2A] = 0x1F22, + [0x1F2B] = 0x1F23, + [0x1F2C] = 0x1F24, + [0x1F2D] = 0x1F25, + [0x1F2E] = 0x1F26, + [0x1F2F] = 0x1F27, + [0x1F38] = 0x1F30, + [0x1F39] = 0x1F31, + [0x1F3A] = 0x1F32, + [0x1F3B] = 0x1F33, + [0x1F3C] = 0x1F34, + [0x1F3D] = 0x1F35, + [0x1F3E] = 0x1F36, + [0x1F3F] = 0x1F37, + [0x1F48] = 0x1F40, + [0x1F49] = 0x1F41, + [0x1F4A] = 0x1F42, + [0x1F4B] = 0x1F43, + [0x1F4C] = 0x1F44, + [0x1F4D] = 0x1F45, + [0x1F50] = {0x03C5, 0x0313}, + [0x1F52] = {0x03C5, 0x0313, 0x0300}, + [0x1F54] = {0x03C5, 0x0313, 0x0301}, + [0x1F56] = {0x03C5, 0x0313, 0x0342}, + [0x1F59] = 0x1F51, + [0x1F5B] = 0x1F53, + [0x1F5D] = 0x1F55, + [0x1F5F] = 0x1F57, + [0x1F68] = 0x1F60, + [0x1F69] = 0x1F61, + [0x1F6A] = 0x1F62, + [0x1F6B] = 0x1F63, + [0x1F6C] = 0x1F64, + [0x1F6D] = 0x1F65, + [0x1F6E] = 0x1F66, + [0x1F6F] = 0x1F67, + [0x1F80] = {0x1F00, 0x03B9}, + [0x1F81] = {0x1F01, 0x03B9}, + [0x1F82] = {0x1F02, 0x03B9}, + [0x1F83] = {0x1F03, 0x03B9}, + [0x1F84] = {0x1F04, 0x03B9}, + [0x1F85] = {0x1F05, 0x03B9}, + [0x1F86] = {0x1F06, 0x03B9}, + [0x1F87] = {0x1F07, 0x03B9}, + [0x1F88] = {0x1F00, 0x03B9}, + --1F88; S;0x1F80, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI + [0x1F89] = {0x1F01, 0x03B9}, + --1F89; S;0x1F81, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI + [0x1F8A] = {0x1F02, 0x03B9}, + --1F8A; S;0x1F82, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI + [0x1F8B] = {0x1F03, 0x03B9}, + --1F8B; S;0x1F83, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI + [0x1F8C] = {0x1F04, 0x03B9}, + --1F8C; S;0x1F84, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI + [0x1F8D] = {0x1F05, 0x03B9}, + --1F8D; S;0x1F85, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI + [0x1F8E] = {0x1F06, 0x03B9}, + --1F8E; S;0x1F86, ; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI + [0x1F8F] = {0x1F07, 0x03B9}, + --1F8F; S;0x1F87, ; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI + [0x1F90] = {0x1F20, 0x03B9}, + [0x1F91] = {0x1F21, 0x03B9}, + [0x1F92] = {0x1F22, 0x03B9}, + [0x1F93] = {0x1F23, 0x03B9}, + [0x1F94] = {0x1F24, 0x03B9}, + [0x1F95] = {0x1F25, 0x03B9}, + [0x1F96] = {0x1F26, 0x03B9}, + [0x1F97] = {0x1F27, 0x03B9}, + [0x1F98] = {0x1F20, 0x03B9}, + --1F98; S;0x1F90, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI + [0x1F99] = {0x1F21, 0x03B9}, + --1F99; S;0x1F91, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI + [0x1F9A] = {0x1F22, 0x03B9}, + --1F9A; S;0x1F92, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI + [0x1F9B] = {0x1F23, 0x03B9}, + --1F9B; S;0x1F93, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI + [0x1F9C] = {0x1F24, 0x03B9}, + --1F9C; S;0x1F94, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI + [0x1F9D] = {0x1F25, 0x03B9}, + --1F9D; S;0x1F95, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI + [0x1F9E] = {0x1F26, 0x03B9}, + --1F9E; S;0x1F96, ; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI + [0x1F9F] = {0x1F27, 0x03B9}, + --1F9F; S;0x1F97, ; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI + [0x1FA0] = {0x1F60, 0x03B9}, + [0x1FA1] = {0x1F61, 0x03B9}, + [0x1FA2] = {0x1F62, 0x03B9}, + [0x1FA3] = {0x1F63, 0x03B9}, + [0x1FA4] = {0x1F64, 0x03B9}, + [0x1FA5] = {0x1F65, 0x03B9}, + [0x1FA6] = {0x1F66, 0x03B9}, + [0x1FA7] = {0x1F67, 0x03B9}, + [0x1FA8] = {0x1F60, 0x03B9}, + --1FA8; S;0x1FA0, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI + [0x1FA9] = {0x1F61, 0x03B9}, + --1FA9; S;0x1FA1, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI + [0x1FAA] = {0x1F62, 0x03B9}, + --1FAA; S;0x1FA2, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI + [0x1FAB] = {0x1F63, 0x03B9}, + --1FAB; S;0x1FA3, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI + [0x1FAC] = {0x1F64, 0x03B9}, + --1FAC; S;0x1FA4, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI + [0x1FAD] = {0x1F65, 0x03B9}, + --1FAD; S;0x1FA5, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI + [0x1FAE] = {0x1F66, 0x03B9}, + --1FAE; S;0x1FA6, ; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI + [0x1FAF] = {0x1F67, 0x03B9}, + --1FAF; S;0x1FA7, ; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI + [0x1FB2] = {0x1F70, 0x03B9}, + [0x1FB3] = {0x03B1, 0x03B9}, + [0x1FB4] = {0x03AC, 0x03B9}, + [0x1FB6] = {0x03B1, 0x0342}, + [0x1FB7] = {0x03B1, 0x0342, 0x03B9}, + [0x1FB8] = 0x1FB0, + [0x1FB9] = 0x1FB1, + [0x1FBA] = 0x1F70, + [0x1FBB] = 0x1F71, + [0x1FBC] = {0x03B1, 0x03B9}, + --1FBC; S;0x1FB3, ; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI + [0x1FBE] = 0x03B9, + [0x1FC2] = {0x1F74, 0x03B9}, + [0x1FC3] = {0x03B7, 0x03B9}, + [0x1FC4] = {0x03AE, 0x03B9}, + [0x1FC6] = {0x03B7, 0x0342}, + [0x1FC7] = {0x03B7, 0x0342, 0x03B9}, + [0x1FC8] = 0x1F72, + [0x1FC9] = 0x1F73, + [0x1FCA] = 0x1F74, + [0x1FCB] = 0x1F75, + [0x1FCC] = {0x03B7, 0x03B9}, + --1FCC; S;0x1FC3, ; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI + [0x1FD2] = {0x03B9, 0x0308, 0x0300}, + [0x1FD3] = {0x03B9, 0x0308, 0x0301}, + [0x1FD6] = {0x03B9, 0x0342}, + [0x1FD7] = {0x03B9, 0x0308, 0x0342}, + [0x1FD8] = 0x1FD0, + [0x1FD9] = 0x1FD1, + [0x1FDA] = 0x1F76, + [0x1FDB] = 0x1F77, + [0x1FE2] = {0x03C5, 0x0308, 0x0300}, + [0x1FE3] = {0x03C5, 0x0308, 0x0301}, + [0x1FE4] = {0x03C1, 0x0313}, + [0x1FE6] = {0x03C5, 0x0342}, + [0x1FE7] = {0x03C5, 0x0308, 0x0342}, + [0x1FE8] = 0x1FE0, + [0x1FE9] = 0x1FE1, + [0x1FEA] = 0x1F7A, + [0x1FEB] = 0x1F7B, + [0x1FEC] = 0x1FE5, + [0x1FF2] = {0x1F7C, 0x03B9}, + [0x1FF3] = {0x03C9, 0x03B9}, + [0x1FF4] = {0x03CE, 0x03B9}, + [0x1FF6] = {0x03C9, 0x0342}, + [0x1FF7] = {0x03C9, 0x0342, 0x03B9}, + [0x1FF8] = 0x1F78, + [0x1FF9] = 0x1F79, + [0x1FFA] = 0x1F7C, + [0x1FFB] = 0x1F7D, + [0x1FFC] = {0x03C9, 0x03B9}, + --1FFC; S;0x1FF3, ; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI + [0x2126] = 0x03C9, + [0x212A] = 0x006B, + [0x212B] = 0x00E5, + [0x2132] = 0x214E, + [0x2160] = 0x2170, + [0x2161] = 0x2171, + [0x2162] = 0x2172, + [0x2163] = 0x2173, + [0x2164] = 0x2174, + [0x2165] = 0x2175, + [0x2166] = 0x2176, + [0x2167] = 0x2177, + [0x2168] = 0x2178, + [0x2169] = 0x2179, + [0x216A] = 0x217A, + [0x216B] = 0x217B, + [0x216C] = 0x217C, + [0x216D] = 0x217D, + [0x216E] = 0x217E, + [0x216F] = 0x217F, + [0x2183] = 0x2184, + [0x24B6] = 0x24D0, + [0x24B7] = 0x24D1, + [0x24B8] = 0x24D2, + [0x24B9] = 0x24D3, + [0x24BA] = 0x24D4, + [0x24BB] = 0x24D5, + [0x24BC] = 0x24D6, + [0x24BD] = 0x24D7, + [0x24BE] = 0x24D8, + [0x24BF] = 0x24D9, + [0x24C0] = 0x24DA, + [0x24C1] = 0x24DB, + [0x24C2] = 0x24DC, + [0x24C3] = 0x24DD, + [0x24C4] = 0x24DE, + [0x24C5] = 0x24DF, + [0x24C6] = 0x24E0, + [0x24C7] = 0x24E1, + [0x24C8] = 0x24E2, + [0x24C9] = 0x24E3, + [0x24CA] = 0x24E4, + [0x24CB] = 0x24E5, + [0x24CC] = 0x24E6, + [0x24CD] = 0x24E7, + [0x24CE] = 0x24E8, + [0x24CF] = 0x24E9, + [0x2C00] = 0x2C30, + [0x2C01] = 0x2C31, + [0x2C02] = 0x2C32, + [0x2C03] = 0x2C33, + [0x2C04] = 0x2C34, + [0x2C05] = 0x2C35, + [0x2C06] = 0x2C36, + [0x2C07] = 0x2C37, + [0x2C08] = 0x2C38, + [0x2C09] = 0x2C39, + [0x2C0A] = 0x2C3A, + [0x2C0B] = 0x2C3B, + [0x2C0C] = 0x2C3C, + [0x2C0D] = 0x2C3D, + [0x2C0E] = 0x2C3E, + [0x2C0F] = 0x2C3F, + [0x2C10] = 0x2C40, + [0x2C11] = 0x2C41, + [0x2C12] = 0x2C42, + [0x2C13] = 0x2C43, + [0x2C14] = 0x2C44, + [0x2C15] = 0x2C45, + [0x2C16] = 0x2C46, + [0x2C17] = 0x2C47, + [0x2C18] = 0x2C48, + [0x2C19] = 0x2C49, + [0x2C1A] = 0x2C4A, + [0x2C1B] = 0x2C4B, + [0x2C1C] = 0x2C4C, + [0x2C1D] = 0x2C4D, + [0x2C1E] = 0x2C4E, + [0x2C1F] = 0x2C4F, + [0x2C20] = 0x2C50, + [0x2C21] = 0x2C51, + [0x2C22] = 0x2C52, + [0x2C23] = 0x2C53, + [0x2C24] = 0x2C54, + [0x2C25] = 0x2C55, + [0x2C26] = 0x2C56, + [0x2C27] = 0x2C57, + [0x2C28] = 0x2C58, + [0x2C29] = 0x2C59, + [0x2C2A] = 0x2C5A, + [0x2C2B] = 0x2C5B, + [0x2C2C] = 0x2C5C, + [0x2C2D] = 0x2C5D, + [0x2C2E] = 0x2C5E, + [0x2C60] = 0x2C61, + [0x2C62] = 0x026B, + [0x2C63] = 0x1D7D, + [0x2C64] = 0x027D, + [0x2C67] = 0x2C68, + [0x2C69] = 0x2C6A, + [0x2C6B] = 0x2C6C, + [0x2C6D] = 0x0251, + [0x2C6E] = 0x0271, + [0x2C6F] = 0x0250, + [0x2C70] = 0x0252, + [0x2C72] = 0x2C73, + [0x2C75] = 0x2C76, + [0x2C7E] = 0x023F, + [0x2C7F] = 0x0240, + [0x2C80] = 0x2C81, + [0x2C82] = 0x2C83, + [0x2C84] = 0x2C85, + [0x2C86] = 0x2C87, + [0x2C88] = 0x2C89, + [0x2C8A] = 0x2C8B, + [0x2C8C] = 0x2C8D, + [0x2C8E] = 0x2C8F, + [0x2C90] = 0x2C91, + [0x2C92] = 0x2C93, + [0x2C94] = 0x2C95, + [0x2C96] = 0x2C97, + [0x2C98] = 0x2C99, + [0x2C9A] = 0x2C9B, + [0x2C9C] = 0x2C9D, + [0x2C9E] = 0x2C9F, + [0x2CA0] = 0x2CA1, + [0x2CA2] = 0x2CA3, + [0x2CA4] = 0x2CA5, + [0x2CA6] = 0x2CA7, + [0x2CA8] = 0x2CA9, + [0x2CAA] = 0x2CAB, + [0x2CAC] = 0x2CAD, + [0x2CAE] = 0x2CAF, + [0x2CB0] = 0x2CB1, + [0x2CB2] = 0x2CB3, + [0x2CB4] = 0x2CB5, + [0x2CB6] = 0x2CB7, + [0x2CB8] = 0x2CB9, + [0x2CBA] = 0x2CBB, + [0x2CBC] = 0x2CBD, + [0x2CBE] = 0x2CBF, + [0x2CC0] = 0x2CC1, + [0x2CC2] = 0x2CC3, + [0x2CC4] = 0x2CC5, + [0x2CC6] = 0x2CC7, + [0x2CC8] = 0x2CC9, + [0x2CCA] = 0x2CCB, + [0x2CCC] = 0x2CCD, + [0x2CCE] = 0x2CCF, + [0x2CD0] = 0x2CD1, + [0x2CD2] = 0x2CD3, + [0x2CD4] = 0x2CD5, + [0x2CD6] = 0x2CD7, + [0x2CD8] = 0x2CD9, + [0x2CDA] = 0x2CDB, + [0x2CDC] = 0x2CDD, + [0x2CDE] = 0x2CDF, + [0x2CE0] = 0x2CE1, + [0x2CE2] = 0x2CE3, + [0x2CEB] = 0x2CEC, + [0x2CED] = 0x2CEE, + [0x2CF2] = 0x2CF3, + [0xA640] = 0xA641, + [0xA642] = 0xA643, + [0xA644] = 0xA645, + [0xA646] = 0xA647, + [0xA648] = 0xA649, + [0xA64A] = 0xA64B, + [0xA64C] = 0xA64D, + [0xA64E] = 0xA64F, + [0xA650] = 0xA651, + [0xA652] = 0xA653, + [0xA654] = 0xA655, + [0xA656] = 0xA657, + [0xA658] = 0xA659, + [0xA65A] = 0xA65B, + [0xA65C] = 0xA65D, + [0xA65E] = 0xA65F, + [0xA660] = 0xA661, + [0xA662] = 0xA663, + [0xA664] = 0xA665, + [0xA666] = 0xA667, + [0xA668] = 0xA669, + [0xA66A] = 0xA66B, + [0xA66C] = 0xA66D, + [0xA680] = 0xA681, + [0xA682] = 0xA683, + [0xA684] = 0xA685, + [0xA686] = 0xA687, + [0xA688] = 0xA689, + [0xA68A] = 0xA68B, + [0xA68C] = 0xA68D, + [0xA68E] = 0xA68F, + [0xA690] = 0xA691, + [0xA692] = 0xA693, + [0xA694] = 0xA695, + [0xA696] = 0xA697, + [0xA698] = 0xA699, + [0xA69A] = 0xA69B, + [0xA722] = 0xA723, + [0xA724] = 0xA725, + [0xA726] = 0xA727, + [0xA728] = 0xA729, + [0xA72A] = 0xA72B, + [0xA72C] = 0xA72D, + [0xA72E] = 0xA72F, + [0xA732] = 0xA733, + [0xA734] = 0xA735, + [0xA736] = 0xA737, + [0xA738] = 0xA739, + [0xA73A] = 0xA73B, + [0xA73C] = 0xA73D, + [0xA73E] = 0xA73F, + [0xA740] = 0xA741, + [0xA742] = 0xA743, + [0xA744] = 0xA745, + [0xA746] = 0xA747, + [0xA748] = 0xA749, + [0xA74A] = 0xA74B, + [0xA74C] = 0xA74D, + [0xA74E] = 0xA74F, + [0xA750] = 0xA751, + [0xA752] = 0xA753, + [0xA754] = 0xA755, + [0xA756] = 0xA757, + [0xA758] = 0xA759, + [0xA75A] = 0xA75B, + [0xA75C] = 0xA75D, + [0xA75E] = 0xA75F, + [0xA760] = 0xA761, + [0xA762] = 0xA763, + [0xA764] = 0xA765, + [0xA766] = 0xA767, + [0xA768] = 0xA769, + [0xA76A] = 0xA76B, + [0xA76C] = 0xA76D, + [0xA76E] = 0xA76F, + [0xA779] = 0xA77A, + [0xA77B] = 0xA77C, + [0xA77D] = 0x1D79, + [0xA77E] = 0xA77F, + [0xA780] = 0xA781, + [0xA782] = 0xA783, + [0xA784] = 0xA785, + [0xA786] = 0xA787, + [0xA78B] = 0xA78C, + [0xA78D] = 0x0265, + [0xA790] = 0xA791, + [0xA792] = 0xA793, + [0xA796] = 0xA797, + [0xA798] = 0xA799, + [0xA79A] = 0xA79B, + [0xA79C] = 0xA79D, + [0xA79E] = 0xA79F, + [0xA7A0] = 0xA7A1, + [0xA7A2] = 0xA7A3, + [0xA7A4] = 0xA7A5, + [0xA7A6] = 0xA7A7, + [0xA7A8] = 0xA7A9, + [0xA7AA] = 0x0266, + [0xA7AB] = 0x025C, + [0xA7AC] = 0x0261, + [0xA7AD] = 0x026C, + [0xA7AE] = 0x026A, + [0xA7B0] = 0x029E, + [0xA7B1] = 0x0287, + [0xA7B2] = 0x029D, + [0xA7B3] = 0xAB53, + [0xA7B4] = 0xA7B5, + [0xA7B6] = 0xA7B7, + [0xA7B8] = 0xA7B9, + [0xA7BA] = 0xA7BB, + [0xA7BC] = 0xA7BD, + [0xA7BE] = 0xA7BF, + [0xA7C2] = 0xA7C3, + [0xA7C4] = 0xA794, + [0xA7C5] = 0x0282, + [0xA7C6] = 0x1D8E, + [0xA7C7] = 0xA7C8, + [0xA7C9] = 0xA7CA, + [0xA7F5] = 0xA7F6, + [0xAB70] = 0x13A0, + [0xAB71] = 0x13A1, + [0xAB72] = 0x13A2, + [0xAB73] = 0x13A3, + [0xAB74] = 0x13A4, + [0xAB75] = 0x13A5, + [0xAB76] = 0x13A6, + [0xAB77] = 0x13A7, + [0xAB78] = 0x13A8, + [0xAB79] = 0x13A9, + [0xAB7A] = 0x13AA, + [0xAB7B] = 0x13AB, + [0xAB7C] = 0x13AC, + [0xAB7D] = 0x13AD, + [0xAB7E] = 0x13AE, + [0xAB7F] = 0x13AF, + [0xAB80] = 0x13B0, + [0xAB81] = 0x13B1, + [0xAB82] = 0x13B2, + [0xAB83] = 0x13B3, + [0xAB84] = 0x13B4, + [0xAB85] = 0x13B5, + [0xAB86] = 0x13B6, + [0xAB87] = 0x13B7, + [0xAB88] = 0x13B8, + [0xAB89] = 0x13B9, + [0xAB8A] = 0x13BA, + [0xAB8B] = 0x13BB, + [0xAB8C] = 0x13BC, + [0xAB8D] = 0x13BD, + [0xAB8E] = 0x13BE, + [0xAB8F] = 0x13BF, + [0xAB90] = 0x13C0, + [0xAB91] = 0x13C1, + [0xAB92] = 0x13C2, + [0xAB93] = 0x13C3, + [0xAB94] = 0x13C4, + [0xAB95] = 0x13C5, + [0xAB96] = 0x13C6, + [0xAB97] = 0x13C7, + [0xAB98] = 0x13C8, + [0xAB99] = 0x13C9, + [0xAB9A] = 0x13CA, + [0xAB9B] = 0x13CB, + [0xAB9C] = 0x13CC, + [0xAB9D] = 0x13CD, + [0xAB9E] = 0x13CE, + [0xAB9F] = 0x13CF, + [0xABA0] = 0x13D0, + [0xABA1] = 0x13D1, + [0xABA2] = 0x13D2, + [0xABA3] = 0x13D3, + [0xABA4] = 0x13D4, + [0xABA5] = 0x13D5, + [0xABA6] = 0x13D6, + [0xABA7] = 0x13D7, + [0xABA8] = 0x13D8, + [0xABA9] = 0x13D9, + [0xABAA] = 0x13DA, + [0xABAB] = 0x13DB, + [0xABAC] = 0x13DC, + [0xABAD] = 0x13DD, + [0xABAE] = 0x13DE, + [0xABAF] = 0x13DF, + [0xABB0] = 0x13E0, + [0xABB1] = 0x13E1, + [0xABB2] = 0x13E2, + [0xABB3] = 0x13E3, + [0xABB4] = 0x13E4, + [0xABB5] = 0x13E5, + [0xABB6] = 0x13E6, + [0xABB7] = 0x13E7, + [0xABB8] = 0x13E8, + [0xABB9] = 0x13E9, + [0xABBA] = 0x13EA, + [0xABBB] = 0x13EB, + [0xABBC] = 0x13EC, + [0xABBD] = 0x13ED, + [0xABBE] = 0x13EE, + [0xABBF] = 0x13EF, + [0xFB00] = {0x0066, 0x0066}, + [0xFB01] = {0x0066, 0x0069}, + [0xFB02] = {0x0066, 0x006C}, + [0xFB03] = {0x0066, 0x0066, 0x0069}, + [0xFB04] = {0x0066, 0x0066, 0x006C}, + [0xFB05] = {0x0073, 0x0074}, + [0xFB06] = {0x0073, 0x0074}, + [0xFB13] = {0x0574, 0x0576}, + [0xFB14] = {0x0574, 0x0565}, + [0xFB15] = {0x0574, 0x056B}, + [0xFB16] = {0x057E, 0x0576}, + [0xFB17] = {0x0574, 0x056D}, + [0xFF21] = 0xFF41, + [0xFF22] = 0xFF42, + [0xFF23] = 0xFF43, + [0xFF24] = 0xFF44, + [0xFF25] = 0xFF45, + [0xFF26] = 0xFF46, + [0xFF27] = 0xFF47, + [0xFF28] = 0xFF48, + [0xFF29] = 0xFF49, + [0xFF2A] = 0xFF4A, + [0xFF2B] = 0xFF4B, + [0xFF2C] = 0xFF4C, + [0xFF2D] = 0xFF4D, + [0xFF2E] = 0xFF4E, + [0xFF2F] = 0xFF4F, + [0xFF30] = 0xFF50, + [0xFF31] = 0xFF51, + [0xFF32] = 0xFF52, + [0xFF33] = 0xFF53, + [0xFF34] = 0xFF54, + [0xFF35] = 0xFF55, + [0xFF36] = 0xFF56, + [0xFF37] = 0xFF57, + [0xFF38] = 0xFF58, + [0xFF39] = 0xFF59, + [0xFF3A] = 0xFF5A, + [0x10400] = 0x10428, + [0x10401] = 0x10429, + [0x10402] = 0x1042A, + [0x10403] = 0x1042B, + [0x10404] = 0x1042C, + [0x10405] = 0x1042D, + [0x10406] = 0x1042E, + [0x10407] = 0x1042F, + [0x10408] = 0x10430, + [0x10409] = 0x10431, + [0x1040A] = 0x10432, + [0x1040B] = 0x10433, + [0x1040C] = 0x10434, + [0x1040D] = 0x10435, + [0x1040E] = 0x10436, + [0x1040F] = 0x10437, + [0x10410] = 0x10438, + [0x10411] = 0x10439, + [0x10412] = 0x1043A, + [0x10413] = 0x1043B, + [0x10414] = 0x1043C, + [0x10415] = 0x1043D, + [0x10416] = 0x1043E, + [0x10417] = 0x1043F, + [0x10418] = 0x10440, + [0x10419] = 0x10441, + [0x1041A] = 0x10442, + [0x1041B] = 0x10443, + [0x1041C] = 0x10444, + [0x1041D] = 0x10445, + [0x1041E] = 0x10446, + [0x1041F] = 0x10447, + [0x10420] = 0x10448, + [0x10421] = 0x10449, + [0x10422] = 0x1044A, + [0x10423] = 0x1044B, + [0x10424] = 0x1044C, + [0x10425] = 0x1044D, + [0x10426] = 0x1044E, + [0x10427] = 0x1044F, + [0x104B0] = 0x104D8, + [0x104B1] = 0x104D9, + [0x104B2] = 0x104DA, + [0x104B3] = 0x104DB, + [0x104B4] = 0x104DC, + [0x104B5] = 0x104DD, + [0x104B6] = 0x104DE, + [0x104B7] = 0x104DF, + [0x104B8] = 0x104E0, + [0x104B9] = 0x104E1, + [0x104BA] = 0x104E2, + [0x104BB] = 0x104E3, + [0x104BC] = 0x104E4, + [0x104BD] = 0x104E5, + [0x104BE] = 0x104E6, + [0x104BF] = 0x104E7, + [0x104C0] = 0x104E8, + [0x104C1] = 0x104E9, + [0x104C2] = 0x104EA, + [0x104C3] = 0x104EB, + [0x104C4] = 0x104EC, + [0x104C5] = 0x104ED, + [0x104C6] = 0x104EE, + [0x104C7] = 0x104EF, + [0x104C8] = 0x104F0, + [0x104C9] = 0x104F1, + [0x104CA] = 0x104F2, + [0x104CB] = 0x104F3, + [0x104CC] = 0x104F4, + [0x104CD] = 0x104F5, + [0x104CE] = 0x104F6, + [0x104CF] = 0x104F7, + [0x104D0] = 0x104F8, + [0x104D1] = 0x104F9, + [0x104D2] = 0x104FA, + [0x104D3] = 0x104FB, + [0x10C80] = 0x10CC0, + [0x10C81] = 0x10CC1, + [0x10C82] = 0x10CC2, + [0x10C83] = 0x10CC3, + [0x10C84] = 0x10CC4, + [0x10C85] = 0x10CC5, + [0x10C86] = 0x10CC6, + [0x10C87] = 0x10CC7, + [0x10C88] = 0x10CC8, + [0x10C89] = 0x10CC9, + [0x10C8A] = 0x10CCA, + [0x10C8B] = 0x10CCB, + [0x10C8C] = 0x10CCC, + [0x10C8D] = 0x10CCD, + [0x10C8E] = 0x10CCE, + [0x10C8F] = 0x10CCF, + [0x10C90] = 0x10CD0, + [0x10C91] = 0x10CD1, + [0x10C92] = 0x10CD2, + [0x10C93] = 0x10CD3, + [0x10C94] = 0x10CD4, + [0x10C95] = 0x10CD5, + [0x10C96] = 0x10CD6, + [0x10C97] = 0x10CD7, + [0x10C98] = 0x10CD8, + [0x10C99] = 0x10CD9, + [0x10C9A] = 0x10CDA, + [0x10C9B] = 0x10CDB, + [0x10C9C] = 0x10CDC, + [0x10C9D] = 0x10CDD, + [0x10C9E] = 0x10CDE, + [0x10C9F] = 0x10CDF, + [0x10CA0] = 0x10CE0, + [0x10CA1] = 0x10CE1, + [0x10CA2] = 0x10CE2, + [0x10CA3] = 0x10CE3, + [0x10CA4] = 0x10CE4, + [0x10CA5] = 0x10CE5, + [0x10CA6] = 0x10CE6, + [0x10CA7] = 0x10CE7, + [0x10CA8] = 0x10CE8, + [0x10CA9] = 0x10CE9, + [0x10CAA] = 0x10CEA, + [0x10CAB] = 0x10CEB, + [0x10CAC] = 0x10CEC, + [0x10CAD] = 0x10CED, + [0x10CAE] = 0x10CEE, + [0x10CAF] = 0x10CEF, + [0x10CB0] = 0x10CF0, + [0x10CB1] = 0x10CF1, + [0x10CB2] = 0x10CF2, + [0x118A0] = 0x118C0, + [0x118A1] = 0x118C1, + [0x118A2] = 0x118C2, + [0x118A3] = 0x118C3, + [0x118A4] = 0x118C4, + [0x118A5] = 0x118C5, + [0x118A6] = 0x118C6, + [0x118A7] = 0x118C7, + [0x118A8] = 0x118C8, + [0x118A9] = 0x118C9, + [0x118AA] = 0x118CA, + [0x118AB] = 0x118CB, + [0x118AC] = 0x118CC, + [0x118AD] = 0x118CD, + [0x118AE] = 0x118CE, + [0x118AF] = 0x118CF, + [0x118B0] = 0x118D0, + [0x118B1] = 0x118D1, + [0x118B2] = 0x118D2, + [0x118B3] = 0x118D3, + [0x118B4] = 0x118D4, + [0x118B5] = 0x118D5, + [0x118B6] = 0x118D6, + [0x118B7] = 0x118D7, + [0x118B8] = 0x118D8, + [0x118B9] = 0x118D9, + [0x118BA] = 0x118DA, + [0x118BB] = 0x118DB, + [0x118BC] = 0x118DC, + [0x118BD] = 0x118DD, + [0x118BE] = 0x118DE, + [0x118BF] = 0x118DF, + [0x16E40] = 0x16E60, + [0x16E41] = 0x16E61, + [0x16E42] = 0x16E62, + [0x16E43] = 0x16E63, + [0x16E44] = 0x16E64, + [0x16E45] = 0x16E65, + [0x16E46] = 0x16E66, + [0x16E47] = 0x16E67, + [0x16E48] = 0x16E68, + [0x16E49] = 0x16E69, + [0x16E4A] = 0x16E6A, + [0x16E4B] = 0x16E6B, + [0x16E4C] = 0x16E6C, + [0x16E4D] = 0x16E6D, + [0x16E4E] = 0x16E6E, + [0x16E4F] = 0x16E6F, + [0x16E50] = 0x16E70, + [0x16E51] = 0x16E71, + [0x16E52] = 0x16E72, + [0x16E53] = 0x16E73, + [0x16E54] = 0x16E74, + [0x16E55] = 0x16E75, + [0x16E56] = 0x16E76, + [0x16E57] = 0x16E77, + [0x16E58] = 0x16E78, + [0x16E59] = 0x16E79, + [0x16E5A] = 0x16E7A, + [0x16E5B] = 0x16E7B, + [0x16E5C] = 0x16E7C, + [0x16E5D] = 0x16E7D, + [0x16E5E] = 0x16E7E, + [0x16E5F] = 0x16E7F, + [0x1E900] = 0x1E922, + [0x1E901] = 0x1E923, + [0x1E902] = 0x1E924, + [0x1E903] = 0x1E925, + [0x1E904] = 0x1E926, + [0x1E905] = 0x1E927, + [0x1E906] = 0x1E928, + [0x1E907] = 0x1E929, + [0x1E908] = 0x1E92A, + [0x1E909] = 0x1E92B, + [0x1E90A] = 0x1E92C, + [0x1E90B] = 0x1E92D, + [0x1E90C] = 0x1E92E, + [0x1E90D] = 0x1E92F, + [0x1E90E] = 0x1E930, + [0x1E90F] = 0x1E931, + [0x1E910] = 0x1E932, + [0x1E911] = 0x1E933, + [0x1E912] = 0x1E934, + [0x1E913] = 0x1E935, + [0x1E914] = 0x1E936, + [0x1E915] = 0x1E937, + [0x1E916] = 0x1E938, + [0x1E917] = 0x1E939, + [0x1E918] = 0x1E93A, + [0x1E919] = 0x1E93B, + [0x1E91A] = 0x1E93C, + [0x1E91B] = 0x1E93D, + [0x1E91C] = 0x1E93E, + [0x1E91D] = 0x1E93F, + [0x1E91E] = 0x1E940, + [0x1E91F] = 0x1E941, + [0x1E920] = 0x1E942, + [0x1E921] = 0x1E943, +} +for k,v in pairs(utf8_case_conv_utl) do utf8_case_conv_ltu[v] = k end + +return UTFString \ No newline at end of file diff --git a/Capy64/Assets/bios.lua b/Capy64/Assets/bios.lua new file mode 100644 index 0000000..e2d9330 --- /dev/null +++ b/Capy64/Assets/bios.lua @@ -0,0 +1,122 @@ +local term = require("term") +local timer = require("timer") + +local bootSleep = 2000 +local bg = 0x0 +local fg = 0xffffff + +local function sleep(n) + local timerId = timer.start(n) + repeat + local ev, par = coroutine.yield("timer") + until par == timerId +end + +local function writeCenter(text) + local w, h = term.getSize() + local _, y = term.getPos() + term.setPos( + (1 + w / 2) - (#text / 2), + y + ) + term.write(text) +end + +local w, h = term.getSize() + +term.setBlink(false) + +local function setupScreen() + local options = { + { + "Open data folder", + openDataFolder, + }, + { + "Install default OS", + installOS, + }, + { + "Exit setup", + exit, + }, + { + "Shutdown", + os.shutdown, + } + } + + local selection = 1 + local function redraw() + term.setForeground(fg) + term.setBackground(bg) + term.clear() + term.setPos(1,2) + writeCenter("Capy64 Setup") + + term.setPos(1,3) + + for k, v in ipairs(options) do + local _, y = term.getPos() + term.setPos(1, y + 1) + term.clearLine() + + if selection == k then + writeCenter("[ " .. v[1] .. " ]") + else + writeCenter(v[1]) + end + end + end + + while true do + redraw() + local ev = { coroutine.yield("key_down") } + if ev[3] == "up" then + selection = selection - 1 + elseif ev[3] == "down" then + selection = selection + 1 + elseif ev[3] == "enter" then + options[selection][2]() + elseif ev[3] == "esc" then + exit() + end + + if selection > #options then + selection = 1 + elseif selection < 1 then + selection = #options + end + end + +end + +local function bootScreen() + term.setPos(1,2) + writeCenter("Capy64") + term.setPos(1,4) + writeCenter("Powered by Capybaras") + + term.setPos(1, h - 1) + writeCenter("Press F2 to open setup") + + local timerId = timer.start(bootSleep) + while true do + local ev = {coroutine.yield("timer", "key_down")} + if ev[1] == "timer" and ev[2] == timerId then + exit() + elseif ev[1] == "key_down" and ev[3] == "f2" then + setupScreen() + break + end + end +end + +bootScreen() + +term.clear() +exit() + +while true do + coroutine.yield() +end \ No newline at end of file diff --git a/Capy64/Assets/font.ttf b/Capy64/Assets/font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..efad9d2e3147931809357912b48f6b6ade0157ed GIT binary patch literal 365304 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�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.NativePlugins); + allPlugins.AddRange(_game.Plugins); + foreach (var plugin in allPlugins) + { + plugin.LuaInit(_game.LuaRuntime.Thread); + } + } + + public void InstallOS(bool force = false) + { + var installedFilePath = Path.Combine(FileSystem.BasePath, ".installed"); + if (!File.Exists(installedFilePath) || force) + { + FileSystem.CopyDirectory("Assets/Lua", FileSystem.DataPath, true, true); + File.Create(installedFilePath).Dispose(); + } + } + + public static void Shutdown() + { + _game.Exit(); + } + + private int L_OpenDataFolder(IntPtr state) + { + var path = FileSystem.DataPath; + switch (Environment.OSVersion.Platform) + { + case PlatformID.Win32NT: + Process.Start("explorer.exe", path); + break; + case PlatformID.Unix: + Process.Start("xdg-open", path); + break; + } + + return 0; + } + + private int L_InstallOS(IntPtr state) + { + InstallOS(true); + return 0; + } + + private int L_Exit(IntPtr state) + { + CloseRuntime = true; + return 0; + } +} diff --git a/Capy64/BIOS/PanicScreen.cs b/Capy64/BIOS/PanicScreen.cs new file mode 100644 index 0000000..b2c3077 --- /dev/null +++ b/Capy64/BIOS/PanicScreen.cs @@ -0,0 +1,61 @@ +using Capy64.Core; +using Capy64.LuaRuntime.Libraries; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.BIOS; + +public class PanicScreen +{ + public Color ForegroundColor = Color.White; + public Color BackgroundColor = new Color(0, 51, 187); + + private Drawing _drawing; + public PanicScreen(Drawing drawing) + { + _drawing = drawing; + } + + public void Render(string error, string details = null) + { + Term.ForegroundColor = ForegroundColor; + Term.BackgroundColor = BackgroundColor; + Term.SetCursorBlink(false); + Term.SetSize(57, 23); + Term.Clear(); + + var title = " Capy64 "; + var halfX = Term.Width / 2 + 1; + Term.SetCursorPosition(halfX - title.Length / 2, 2); + Term.ForegroundColor = BackgroundColor; + Term.BackgroundColor = ForegroundColor; + Term.Write(title); + + Term.ForegroundColor = ForegroundColor; + Term.BackgroundColor = BackgroundColor; + Term.SetCursorPosition(1, 4); + Print(error + '\n'); + + if (details is not null) { + Print(details); + } + } + + private void Print(string txt) + { + foreach (var ch in txt) + { + Term.Write(ch.ToString()); + if(Term.CursorPosition.X >= Term.Width || ch == '\n') + { + Term.SetCursorPosition(1, (int)Term.CursorPosition.Y + 1); + } + } + Term.SetCursorPosition(1, (int)Term.CursorPosition.Y + 1); + } +} diff --git a/Capy64/BIOS/RuntimeInputEvents.cs b/Capy64/BIOS/RuntimeInputEvents.cs new file mode 100644 index 0000000..eb7279f --- /dev/null +++ b/Capy64/BIOS/RuntimeInputEvents.cs @@ -0,0 +1,127 @@ +using Capy64.Eventing.Events; +using Capy64.Eventing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Capy64.LuaRuntime; +using Microsoft.Xna.Framework.Input; + +namespace Capy64.BIOS; + +internal class RuntimeInputEvents +{ + private EventEmitter _eventEmitter; + private Runtime _runtime; + + public RuntimeInputEvents(EventEmitter eventEmitter, Runtime runtime) + { + _eventEmitter = eventEmitter; + _runtime = runtime; + } + + public void Register() + { + _eventEmitter.OnMouseUp += OnMouseUp; + _eventEmitter.OnMouseDown += OnMouseDown; + _eventEmitter.OnMouseMove += OnMouseMove; + _eventEmitter.OnMouseWheel += OnMouseWheel; + + _eventEmitter.OnKeyUp += OnKeyUp; + _eventEmitter.OnKeyDown += OnKeyDown; + _eventEmitter.OnChar += OnChar; + } + + public void Unregister() + { + _eventEmitter.OnMouseUp -= OnMouseUp; + _eventEmitter.OnMouseDown -= OnMouseDown; + _eventEmitter.OnMouseMove -= OnMouseMove; + _eventEmitter.OnMouseWheel -= OnMouseWheel; + + _eventEmitter.OnKeyUp -= OnKeyUp; + _eventEmitter.OnKeyDown -= OnKeyDown; + _eventEmitter.OnChar -= OnChar; + } + + private void OnMouseUp(object sender, MouseButtonEvent e) + { + _runtime.PushEvent("mouse_up", new object[] + { + (int)e.Button, + e.Position.X, + e.Position.Y, + }); + } + private void OnMouseDown(object sender, MouseButtonEvent e) + { + _runtime.PushEvent("mouse_down", new object[] + { + (int)e.Button, + e.Position.X, + e.Position.Y, + }); + } + + private void OnMouseMove(object sender, MouseMoveEvent e) + { + _runtime.PushEvent("mouse_move", new object[] + { + e.PressedButtons, + e.Position.X, + e.Position.Y, + }); + } + private void OnMouseWheel(object sender, MouseWheelEvent e) + { + _runtime.PushEvent("mouse_scroll", new object[] + { + e.Position.X, + e.Position.Y, + e.VerticalValue, + e.HorizontalValue, + }); + } + + private void OnKeyUp(object sender, KeyEvent e) + { + _runtime.PushEvent("key_up", new object[] + { + e.KeyCode, + e.KeyName, + (int)e.Mods + }); + } + private void OnKeyDown(object sender, KeyEvent e) + { + _runtime.PushEvent("key_down", new object[] + { + e.KeyCode, + e.KeyName, + (int)e.Mods, + e.IsHeld, + }); + + if (e.Mods.HasFlag(InputManager.Modifiers.LCtrl) || e.Mods.HasFlag(InputManager.Modifiers.RCtrl) && !e.IsHeld) + { + if (e.Key == Keys.C) + { + _runtime.PushEvent(new LuaEvent() + { + Name = "interrupt", + Parameters = { }, + BypassFilter = true, + }); + } + } + } + + private void OnChar(object sender, CharEvent e) + { + _runtime.PushEvent("char", new object[] + { + e.Character, + }); + } +} diff --git a/Capy64/Capy64.cs b/Capy64/Capy64.cs new file mode 100644 index 0000000..477e95c --- /dev/null +++ b/Capy64/Capy64.cs @@ -0,0 +1,171 @@ +using Capy64.API; +using Capy64.Core; +using Capy64.Eventing; +using Capy64.Extensions; +using Capy64.Eventing.Events; +using Capy64.LuaRuntime; +using Capy64.PluginManager; +using KeraLua; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using static Capy64.Utils; + +namespace Capy64; + +public class Capy64 : Game, IGame +{ + public Game Game => this; + public string Version => "Capy64 a0.0.1"; + public IList NativePlugins { get; private set; } + public IList Plugins { get; private set; } + public int Width { get; set; } = 400; + public int Height { get; set; } = 300; + public float Scale { get; set; } = 2f; + public Drawing Drawing { get; private set; } + public Runtime LuaRuntime { get; set; } + public EventEmitter EventEmitter { get; private set; } + public Borders Borders = new() + { + Top = 0, + Bottom = 0, + Left = 0, + Right = 0, + }; + + private readonly InputManager _inputManager; + private RenderTarget2D renderTarget; + private readonly GraphicsDeviceManager _graphics; + private SpriteBatch _spriteBatch; + private IServiceProvider _serviceProvider; + private ulong _totalTicks = 0; + + public Capy64() + { + _graphics = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + + EventEmitter = new(); + _inputManager = new(this, EventEmitter); + + Drawing = new(); + } + + public void ConfigureServices(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public void UpdateSize() + { + _graphics.PreferredBackBufferWidth = (int)(Width * Scale) + Borders.Left + Borders.Right; + _graphics.PreferredBackBufferHeight = (int)(Height * Scale) + Borders.Top + Borders.Right; + _graphics.ApplyChanges(); + + renderTarget = new RenderTarget2D( + GraphicsDevice, + Width, + Height, + false, + GraphicsDevice.PresentationParameters.BackBufferFormat, + DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents); + + Drawing.Canvas = renderTarget; + + _inputManager.Texture = renderTarget; + _inputManager.WindowScale = Scale; + + EventEmitter.RaiseScreenSizeChange(); + } + + private void OnWindowSizeChange(object sender, EventArgs e) + { + var bounds = Window.ClientBounds; + Console.WriteLine(bounds); + + if (Window.IsMaximized()) + { + + } + + Width = (int)(bounds.Width / Scale); + Height = (int)(bounds.Height / Scale); + + UpdateSize(); + } + + protected override void Initialize() + { + UpdateSize(); + + Window.AllowUserResizing = false; + Window.ClientSizeChanged += OnWindowSizeChange; + + NativePlugins = GetNativePlugins(); + Plugins = PluginLoader.LoadAllPlugins("plugins", _serviceProvider); + + EventEmitter.RaiseInit(); + + base.Initialize(); + } + + private List GetNativePlugins() + { + var iType = typeof(IPlugin); + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => iType.IsAssignableFrom(p) && !p.IsInterface); + + var plugins = new List(); + + foreach (var type in types) + { + var instance = (IPlugin)ActivatorUtilities.CreateInstance(_serviceProvider, type)!; + plugins.Add(instance); + } + return plugins; + } + + + protected override void LoadContent() + { + _spriteBatch = new SpriteBatch(GraphicsDevice); + } + + protected override void Update(GameTime gameTime) + { + Drawing.Begin(); + + EventEmitter.RaiseTick(new() + { + GameTime = gameTime, + TotalTicks = _totalTicks + }); + + // resume here + + Drawing.End(); + + // Register user input + _inputManager.Update(IsActive); + + base.Update(gameTime); + _totalTicks++; + } + + protected override void Draw(GameTime gameTime) + { + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp); + //GraphicsDevice.Clear(Color.Blue); + _spriteBatch.Draw(renderTarget, new(Borders.Left, Borders.Top), null, Color.White, 0f, Vector2.Zero, Scale, SpriteEffects.None, 0); + _spriteBatch.End(); + + base.Draw(gameTime); + } +} \ No newline at end of file diff --git a/Capy64/Capy64.csproj b/Capy64/Capy64.csproj new file mode 100644 index 0000000..e0e3a05 --- /dev/null +++ b/Capy64/Capy64.csproj @@ -0,0 +1,46 @@ + + + Exe + net7.0 + Major + false + false + + + app.manifest + Icon.ico + true + + + + PreserveNewest + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + \ No newline at end of file diff --git a/Capy64/Content/Content.mgcb b/Capy64/Content/Content.mgcb new file mode 100644 index 0000000..ddc4c36 --- /dev/null +++ b/Capy64/Content/Content.mgcb @@ -0,0 +1,15 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:bin/$(Platform) +/intermediateDir:obj/$(Platform) +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + + +#---------------------------------- Content ---------------------------------# + diff --git a/Capy64/Core/Audio.cs b/Capy64/Core/Audio.cs new file mode 100644 index 0000000..aeadc5f --- /dev/null +++ b/Capy64/Core/Audio.cs @@ -0,0 +1,73 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using System; +using System.Threading.Channels; + +namespace Capy64.Core; + +public class Audio +{ + public const int SampleRate = 48000; + public const int SamplesPerBuffer = 3000; + const int bytesPerSample = 2; + + private readonly DynamicSoundEffectInstance _instance; + private double _time = 0.0; + public Audio() + { + _instance = new(SampleRate, AudioChannels.Mono); + } + + public void test() + { + var workingBuffer = new float[SamplesPerBuffer]; + FillWorkingBuffer(workingBuffer); + var buffer = DivideBuffer(workingBuffer); + _instance.SubmitBuffer(buffer); + } + + private byte[] DivideBuffer(float[] from) + { + var outBuffer = new byte[SamplesPerBuffer * bytesPerSample]; + + for (int i = 0; i < from.Length; i++) { + var floatSample = MathHelper.Clamp(from[i], -1.0f, 1.0f); + var shortSample = (short)(floatSample * short.MaxValue); + + int index = i * bytesPerSample + bytesPerSample; + + if (!BitConverter.IsLittleEndian) + { + outBuffer[index] = (byte)(shortSample >> 8); + outBuffer[index + 1] = (byte)shortSample; + } + else + { + outBuffer[index] = (byte)shortSample; + outBuffer[index + 1] = (byte)(shortSample >> 8); + } + } + + return outBuffer; + } + + public static double SineWave(double time, double frequency) + { + return Math.Sin(time * 2 * Math.PI * frequency); + } + + private void FillWorkingBuffer(float[] buffer) + { + for (int i = 0; i < SamplesPerBuffer; i++) + { + // Here is where you sample your wave function + buffer[i] = (float)SineWave(_time, 440); // Left Channel + + // Advance time passed since beginning + // Since the amount of samples in a second equals the chosen SampleRate + // Then each sample should advance the time by 1 / SampleRate + _time += 1.0 / SampleRate; + } + } + +} diff --git a/Capy64/Core/Drawing.cs b/Capy64/Core/Drawing.cs new file mode 100644 index 0000000..a5be972 --- /dev/null +++ b/Capy64/Core/Drawing.cs @@ -0,0 +1,176 @@ +using FontStashSharp; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Capy64.Core; + +public class Drawing : IDisposable +{ + private SpriteBatch _spriteBatch; + private GraphicsDevice _graphicsDevice; + private readonly FontSystem _fontSystem; + private Texture2D _whitePixel; + private RenderTarget2D _canvas; + private bool _isDrawing; + public RenderTarget2D Canvas + { + get => _canvas; + set + { + var isDrawing = _isDrawing; + if (isDrawing) + End(); + _canvas = value; + _spriteBatch = new SpriteBatch(_canvas.GraphicsDevice); + _graphicsDevice = _canvas.GraphicsDevice; + + _whitePixel = new Texture2D(_spriteBatch.GraphicsDevice, 1, 1, mipmap: false, SurfaceFormat.Color); + _whitePixel.SetData(new Color[1] { Color.White }); + if (isDrawing) + Begin(); + } + } + + public Drawing() + { + _fontSystem = new FontSystem(); + _fontSystem.AddFont(File.ReadAllBytes(@"Assets/font.ttf")); + } + + public void Begin() + { + if (_isDrawing) + return; + + _isDrawing = true; + _graphicsDevice.SetRenderTarget(_canvas); + _graphicsDevice.DepthStencilState = new DepthStencilState() { DepthBufferEnable = true, }; + _spriteBatch.Begin(); + } + + public void End() + { + if (!_isDrawing) + return; + + _spriteBatch.End(); + _graphicsDevice.SetRenderTarget(null); + _isDrawing = false; + } + + public void DrawString(Vector2 pos, string text, Color color, int size = 13) + { + var font = _fontSystem.GetFont(size); + _spriteBatch.DrawString(font, text, pos, color, layerDepth: 0); + } + + public Vector2 MeasureString(string text, int size = 13) + { + var font = _fontSystem.GetFont(size); + return font.MeasureString(text); + } + + public void Plot(Point point, Color color) + { + var grid = new Color[_canvas.Width * _canvas.Height]; + _canvas.GetData(grid); + + if (point.X < 0 || point.Y < 0) return; + if (point.X >= _canvas.Width || point.Y >= _canvas.Height) return; + grid[point.X + point.Y * _canvas.Width] = color; + + _canvas.SetData(grid); + } + + public void Plot(IEnumerable points, Color color) + { + var grid = new Color[_canvas.Width * _canvas.Height]; + _canvas.GetData(grid); + foreach (var point in points) + { + if (point.X < 0 || point.Y < 0) continue; + if (point.X >= _canvas.Width || point.Y >= _canvas.Height) continue; + grid[point.X + point.Y * _canvas.Width] = color; + } + _canvas.SetData(grid); + } + + public void UnsafePlot(Point point, Color color) + { + var grid = new Color[_canvas.Width * _canvas.Height]; + _canvas.GetData(grid); + grid[point.X + point.Y * _canvas.Width] = color; + _canvas.SetData(grid); + } + + public void UnsafePlot(IEnumerable points, Color color) + { + var grid = new Color[_canvas.Width * _canvas.Height]; + _canvas.GetData(grid); + foreach (var point in points) + { + grid[point.X + point.Y * _canvas.Width] = color; + } + _canvas.SetData(grid); + } + + public void DrawPoint(Vector2 point, Color color, int size = 1) + { + _spriteBatch.DrawPoint(point, color, size); + } + + public void DrawCircle(Vector2 pos, int radius, Color color, int thickness = 1) + { + _spriteBatch.DrawCircle(pos, radius, radius * 4, color, thickness); + } + + public void DrawLine(Vector2 start, Vector2 end, Color color, float thickness = 1) + { + _spriteBatch.DrawLine(start, end, color, thickness); + } + + public void DrawRectangle(Vector2 pos, Size2 size, Color color, int thickness = 1, float rotation = 0f) + { + DrawRectangle(new(pos, size), color, thickness, rotation, 0f); + } + + public void DrawPolygon(Vector2 pos, Vector2[] points, Color color, int thickness = 1) + { + _spriteBatch.DrawPolygon(pos, points, color, thickness); + } + + public void DrawEllipse(Vector2 pos, Vector2 radius, Color color, int thickness = 1) + { + var sides = (int)Math.Max(radius.X, radius.Y) * 4; + _spriteBatch.DrawEllipse(pos, radius, sides, color, thickness); + } + + public void Clear(Color? color = default) + { + Color finalColor = color ?? Color.Black; + _graphicsDevice.Clear(finalColor); + } + + public void DrawRectangle(RectangleF rectangle, Color color, float thickness = 1f, float rotation = 0f, float layerDepth = 0f) + { + Vector2 position = new(rectangle.X, rectangle.Y); + Vector2 position2 = new(rectangle.Right - thickness, rectangle.Y); + Vector2 position3 = new(rectangle.X, rectangle.Bottom - thickness); + Vector2 scale = new(rectangle.Width, thickness); + Vector2 scale2 = new(thickness, rectangle.Height); + _spriteBatch.Draw(_whitePixel, position, null, color, rotation, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + _spriteBatch.Draw(_whitePixel, position, null, color, rotation, Vector2.Zero, scale2, SpriteEffects.None, layerDepth); + _spriteBatch.Draw(_whitePixel, position2, null, color, rotation, Vector2.Zero, scale2, SpriteEffects.None, layerDepth); + _spriteBatch.Draw(_whitePixel, position3, null, color, rotation, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + } + + public void Dispose() + { + _spriteBatch.Dispose(); + _whitePixel.Dispose(); + } +} diff --git a/Capy64/Eventing/EventEmitter.cs b/Capy64/Eventing/EventEmitter.cs new file mode 100644 index 0000000..acaf5ea --- /dev/null +++ b/Capy64/Eventing/EventEmitter.cs @@ -0,0 +1,109 @@ +using Capy64.Eventing.Events; +using Microsoft.Xna.Framework.Input; +using System; + + +namespace Capy64.Eventing; + +public class EventEmitter +{ + // Mouse events + public event EventHandler OnMouseDown; + public event EventHandler OnMouseUp; + public event EventHandler OnMouseMove; + public event EventHandler OnMouseWheel; + + // Keyboard events + public event EventHandler OnKeyDown; + public event EventHandler OnKeyUp; + public event EventHandler OnChar; + + // Functional events + public event EventHandler OnTick; + public event EventHandler OnInit; + public event EventHandler OnScreenSizeChange; + + + public void RaiseMouseMove(MouseMoveEvent ev) + { + if (OnMouseMove is not null) + { + OnMouseMove(this, ev); + } + } + + public void RaiseMouseButton(MouseButtonEvent ev) + { + if (ev.State == ButtonState.Released) + { + if (OnMouseUp is not null) + { + OnMouseUp(this, ev); + } + } + else + { + if (OnMouseDown is not null) + { + OnMouseDown(this, ev); + } + } + } + + public void RaiseMouseWheelEvent(MouseWheelEvent ev) + { + if (OnMouseWheel is not null) + { + OnMouseWheel(this, ev); + } + } + + + public void RaiseKeyDown(KeyEvent ev) + { + if (OnKeyDown is not null) + { + OnKeyDown(this, ev); + } + } + + public void RaiseKeyUp(KeyEvent ev) + { + if (OnKeyUp is not null) + { + OnKeyUp(this, ev); + } + } + + public void RaiseCharEvent(CharEvent ev) + { + if (OnChar is not null) + { + OnChar(this, ev); + } + } + + public void RaiseTick(TickEvent ev) + { + if(OnTick is not null) + { + OnTick(this, ev); + } + } + + public void RaiseInit() + { + if(OnInit is not null) + { + OnInit(this, EventArgs.Empty); + } + } + + public void RaiseScreenSizeChange() + { + if(OnScreenSizeChange is not null) + { + OnScreenSizeChange(this, EventArgs.Empty); + } + } +} diff --git a/Capy64/Eventing/Events/CharEvent.cs b/Capy64/Eventing/Events/CharEvent.cs new file mode 100644 index 0000000..d7d3a9f --- /dev/null +++ b/Capy64/Eventing/Events/CharEvent.cs @@ -0,0 +1,8 @@ +using System; + +namespace Capy64.Eventing.Events; + +public class CharEvent : EventArgs +{ + public char Character { get; set; } +} diff --git a/Capy64/Eventing/Events/KeyEvent.cs b/Capy64/Eventing/Events/KeyEvent.cs new file mode 100644 index 0000000..205bab6 --- /dev/null +++ b/Capy64/Eventing/Events/KeyEvent.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework.Input; +using System; +using static Capy64.Eventing.InputManager; + +namespace Capy64.Eventing.Events; + +public class KeyEvent : EventArgs +{ + public int KeyCode { get; set; } + public string KeyName { get; set; } + public bool IsHeld { get; set; } + public Keys Key { get; set; } + public Modifiers Mods { get; set; } +} diff --git a/Capy64/Eventing/Events/MouseButtonEvent.cs b/Capy64/Eventing/Events/MouseButtonEvent.cs new file mode 100644 index 0000000..c5448ec --- /dev/null +++ b/Capy64/Eventing/Events/MouseButtonEvent.cs @@ -0,0 +1,13 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using System; +using static Capy64.Eventing.InputManager; + +namespace Capy64.Eventing.Events; + +public class MouseButtonEvent : EventArgs +{ + public MouseButton Button { get; set; } + public ButtonState State { get; set; } + public Point Position { get; set; } +} diff --git a/Capy64/Eventing/Events/MouseMoveEvent.cs b/Capy64/Eventing/Events/MouseMoveEvent.cs new file mode 100644 index 0000000..d8e92e7 --- /dev/null +++ b/Capy64/Eventing/Events/MouseMoveEvent.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Capy64.Eventing.Events; + +public class MouseMoveEvent : EventArgs +{ + public Point Position { get; set; } + public int[] PressedButtons { get; set; } +} diff --git a/Capy64/Eventing/Events/MouseWheelEvent.cs b/Capy64/Eventing/Events/MouseWheelEvent.cs new file mode 100644 index 0000000..b70137d --- /dev/null +++ b/Capy64/Eventing/Events/MouseWheelEvent.cs @@ -0,0 +1,11 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Capy64.Eventing.Events; + +public class MouseWheelEvent : EventArgs +{ + public Point Position { get; set; } + public int VerticalValue { get; set; } + public int HorizontalValue { get; set; } +} diff --git a/Capy64/Eventing/Events/TickEvent.cs b/Capy64/Eventing/Events/TickEvent.cs new file mode 100644 index 0000000..6d81b78 --- /dev/null +++ b/Capy64/Eventing/Events/TickEvent.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.Eventing.Events; + +public class TickEvent : EventArgs +{ + public GameTime GameTime { get; set; } + public ulong TotalTicks { get; set; } +} diff --git a/Capy64/Eventing/InputManager.cs b/Capy64/Eventing/InputManager.cs new file mode 100644 index 0000000..1606981 --- /dev/null +++ b/Capy64/Eventing/InputManager.cs @@ -0,0 +1,282 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Capy64.Eventing; + +public class InputManager +{ + public enum MouseButton + { + Left = 1, + Right = 2, + Middle = 3, + Button4 = 4, + Button5 = 5, + } + + [Flags] + public enum Modifiers + { + LShift = 1, + RShift = 2, + + LAlt = 4, + RAlt = 8, + + LCtrl = 16, + RCtrl = 32, + } + + private static Keys[] IgnoredTextInputKeys = + { + Keys.Enter, + Keys.Back, + Keys.Tab, + Keys.Delete, + Keys.Escape, + Keys.VolumeDown, + Keys.VolumeUp, + Keys.VolumeMute, + }; + + private readonly Dictionary mouseButtonStates = new() + { + [MouseButton.Left] = ButtonState.Released, + [MouseButton.Right] = ButtonState.Released, + [MouseButton.Middle] = ButtonState.Released, + [MouseButton.Button4] = ButtonState.Released, + [MouseButton.Button5] = ButtonState.Released, + }; + + public Texture2D Texture { get; set; } + public float WindowScale { get; set; } + public const int MouseScrollDelta = 120; + + private Point mousePosition; + private int vMouseScroll; + private int hMouseScroll; + + private Modifiers keyboardMods = 0; + private HashSet pressedKeys = new(); + + private readonly Game _game; + private readonly EventEmitter _eventEmitter; + public InputManager(Game game, EventEmitter eventManager) + + { + _game = game; + _eventEmitter = eventManager; + + _game.Window.KeyDown += OnKeyDown; + _game.Window.KeyUp += OnKeyUp; + _game.Window.TextInput += OnTextInput; + + var mouseState = Mouse.GetState(); + vMouseScroll = mouseState.ScrollWheelValue; + hMouseScroll = mouseState.HorizontalScrollWheelValue; + } + + public void Update(bool IsActive) + { + UpdateMouse(Mouse.GetState(), IsActive); + UpdateKeyboard(Keyboard.GetState(), IsActive); + } + + private void UpdateMouse(MouseState state, bool isActive) + { + if (!isActive) + return; + + var rawPosition = state.Position; + var pos = new Point((int)(rawPosition.X / WindowScale), (int)(rawPosition.Y / WindowScale)); + + if (pos.X < 0 || pos.Y < 0 || pos.X >= Texture.Width || pos.Y >= Texture.Height) + return; + + if (pos != mousePosition) + { + _eventEmitter.RaiseMouseMove(new() + { + Position = pos, + PressedButtons = mouseButtonStates.Where(q => q.Value == ButtonState.Pressed).Select(q => (int)q.Key).ToArray() + }); + mousePosition = pos; + } + + int vValue = 0; + int hValue = 0; + if (state.ScrollWheelValue != vMouseScroll) + { + var vDelta = vMouseScroll - state.ScrollWheelValue; + vMouseScroll = state.ScrollWheelValue; + vValue = vDelta / MouseScrollDelta; + } + + if (state.HorizontalScrollWheelValue != hMouseScroll) + { + var hDelta = hMouseScroll - state.ScrollWheelValue; + hMouseScroll = state.ScrollWheelValue; + hValue = hDelta / MouseScrollDelta; + } + + if (vValue != 0 || hValue != 0) + { + _eventEmitter.RaiseMouseWheelEvent(new() + { + Position = mousePosition, + VerticalValue = vValue, + HorizontalValue = hValue, + }); + } + + // detect changes in mouse buttons + if (state.LeftButton != mouseButtonStates[MouseButton.Left]) + { + _eventEmitter.RaiseMouseButton(new() + { + Button = MouseButton.Left, + State = state.LeftButton, + Position = mousePosition, + }); + } + + if (state.RightButton != mouseButtonStates[MouseButton.Right]) + { + _eventEmitter.RaiseMouseButton(new() + { + Button = MouseButton.Right, + State = state.RightButton, + Position = mousePosition, + }); + } + + if (state.MiddleButton != mouseButtonStates[MouseButton.Middle]) + { + _eventEmitter.RaiseMouseButton(new() + { + Button = MouseButton.Middle, + State = state.MiddleButton, + Position = mousePosition, + }); + } + + if (state.XButton1 != mouseButtonStates[MouseButton.Button4]) + { + _eventEmitter.RaiseMouseButton(new() + { + Button = MouseButton.Button4, + State = state.XButton1, + Position = mousePosition, + }); + } + + if (state.XButton2 != mouseButtonStates[MouseButton.Button5]) + { + _eventEmitter.RaiseMouseButton(new() + { + Button = MouseButton.Button5, + State = state.XButton2, + Position = mousePosition, + }); + } + + // update mouse states + mouseButtonStates[MouseButton.Left] = state.LeftButton; + mouseButtonStates[MouseButton.Right] = state.RightButton; + mouseButtonStates[MouseButton.Middle] = state.MiddleButton; + mouseButtonStates[MouseButton.Button4] = state.XButton1; + mouseButtonStates[MouseButton.Button5] = state.XButton2; + } + + private void UpdateKeyboard(KeyboardState state, bool isActive) + { + var keys = state.GetPressedKeys(); + + if (keys.Contains(Keys.LeftControl)) + keyboardMods |= Modifiers.LCtrl; + else + keyboardMods &= ~Modifiers.LCtrl; + + if (keys.Contains(Keys.RightControl)) + keyboardMods |= Modifiers.RCtrl; + else + keyboardMods &= ~Modifiers.RCtrl; + + if (keys.Contains(Keys.LeftAlt)) + keyboardMods |= Modifiers.LAlt; + else + keyboardMods &= ~Modifiers.LAlt; + + if (keys.Contains(Keys.RightAlt)) + keyboardMods |= Modifiers.RAlt; + else + keyboardMods &= ~Modifiers.RAlt; + + if (keys.Contains(Keys.LeftShift)) + keyboardMods |= Modifiers.LShift; + else + keyboardMods &= ~Modifiers.LShift; + + if (keys.Contains(Keys.RightShift)) + keyboardMods |= Modifiers.RShift; + else + keyboardMods &= ~Modifiers.RShift; + } + + private void OnKeyDown(object sender, InputKeyEventArgs e) + { + _eventEmitter.RaiseKeyDown(new() + { + KeyCode = (int)e.Key, + KeyName = GetKeyName(e.Key), + IsHeld = pressedKeys.Contains(e.Key), + Key = e.Key, + Mods = keyboardMods, + }); + pressedKeys.Add(e.Key); + } + + private void OnKeyUp(object sender, InputKeyEventArgs e) + { + _eventEmitter.RaiseKeyUp(new() + { + KeyCode = (int)e.Key, + KeyName = GetKeyName(e.Key), + Key = e.Key, + Mods = keyboardMods, + }); + pressedKeys.Remove(e.Key); + } + + private void OnTextInput(object sender, TextInputEventArgs e) + { + if (IgnoredTextInputKeys.Contains(e.Key)) + { + return; + } + _eventEmitter.RaiseCharEvent(new() + { + Character = e.Character, + }); + } + + public static string GetKeyName(Keys key) + { + var keyName = ToUnderscore(key.ToString()); + if (key >= Keys.D0 && key <= Keys.D9) + { + keyName = keyName.TrimStart('d'); + } + keyName = keyName.Replace("oem_", ""); + return keyName; + } + + public static string ToUnderscore(string str) + { + return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower(); + } +} diff --git a/Capy64/Extensions/Bindings/SDL2.cs b/Capy64/Extensions/Bindings/SDL2.cs new file mode 100644 index 0000000..357b016 --- /dev/null +++ b/Capy64/Extensions/Bindings/SDL2.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.Extensions.Bindings; + +public partial class SDL2 +{ + private const string SDL = "SDL2.dll"; + + [LibraryImport(SDL)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + public static partial void SDL_MaximizeWindow(IntPtr window); + + [LibraryImport(SDL)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + public static partial uint SDL_GetWindowFlags(IntPtr window); +} diff --git a/Capy64/Extensions/GameWindowExtensions.cs b/Capy64/Extensions/GameWindowExtensions.cs new file mode 100644 index 0000000..976beba --- /dev/null +++ b/Capy64/Extensions/GameWindowExtensions.cs @@ -0,0 +1,62 @@ +using Capy64.Extensions.Bindings; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.Extensions; + +public static class GameWindowExtensions +{ + /// + /// + /// + [Flags] + public enum WindowFlags : uint + { + SDL_WINDOW_FULLSCREEN = 0x00000001, + SDL_WINDOW_OPENGL = 0x00000002, + SDL_WINDOW_SHOWN = 0x00000004, + SDL_WINDOW_HIDDEN = 0x00000008, + SDL_WINDOW_BORDERLESS = 0x00000010, + SDL_WINDOW_RESIZABLE = 0x00000020, + SDL_WINDOW_MINIMIZED = 0x00000040, + SDL_WINDOW_MAXIMIZED = 0x00000080, + SDL_WINDOW_MOUSE_GRABBED = 0x00000100, + SDL_WINDOW_INPUT_FOCUS = 0x00000200, + SDL_WINDOW_MOUSE_FOCUS = 0x00000400, + SDL_WINDOW_FULLSCREEN_DESKTOP = + (SDL_WINDOW_FULLSCREEN | 0x00001000), + SDL_WINDOW_FOREIGN = 0x00000800, + SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000, + SDL_WINDOW_MOUSE_CAPTURE = 0x00004000, + SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000, + SDL_WINDOW_SKIP_TASKBAR = 0x00010000, + SDL_WINDOW_UTILITY = 0x00020000, + SDL_WINDOW_TOOLTIP = 0x00040000, + SDL_WINDOW_POPUP_MENU = 0x00080000, + SDL_WINDOW_KEYBOARD_GRABBED = 0x00100000, + SDL_WINDOW_VULKAN = 0x10000000, + SDL_WINDOW_METAL = 0x2000000, + + SDL_WINDOW_INPUT_GRABBED = + SDL_WINDOW_MOUSE_GRABBED, + } + + public static WindowFlags GetWindowFlags(this GameWindow window) + { + return (WindowFlags)SDL2.SDL_GetWindowFlags(window.Handle); + } + + public static void MaximizeWindow(this GameWindow window) + { + SDL2.SDL_MaximizeWindow(window.Handle); + } + public static bool IsMaximized(this GameWindow window) + { + return window.GetWindowFlags().HasFlag(WindowFlags.SDL_WINDOW_MAXIMIZED); + } +} diff --git a/Capy64/IGame.cs b/Capy64/IGame.cs new file mode 100644 index 0000000..224a874 --- /dev/null +++ b/Capy64/IGame.cs @@ -0,0 +1,31 @@ +using Capy64.API; +using Capy64.Core; +using Capy64.Eventing; +using Capy64.LuaRuntime; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; + +namespace Capy64; + +public interface IGame +{ + Game Game { get; } + public string Version { get; } + IList NativePlugins { get; } + IList Plugins { get; } + GameWindow Window { get; } + Drawing Drawing { get; } + Runtime LuaRuntime { get; set; } + EventEmitter EventEmitter { get; } + void ConfigureServices(IServiceProvider serviceProvider); + + int Width { get; set; } + int Height { get; set; } + float Scale { get; set; } + void UpdateSize(); + + event EventHandler Exiting; + void Run(); + void Exit(); +} diff --git a/Capy64/Icon.bmp b/Capy64/Icon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2b481653e241818d2894e4ef33234eaff9aad701 GIT binary patch literal 262282 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 RJoIeLznU7AhJ`o~_y6Yy51#-4 literal 0 HcmV?d00001 diff --git a/Capy64/LuaRuntime/Constants.cs b/Capy64/LuaRuntime/Constants.cs new file mode 100644 index 0000000..054a7e0 --- /dev/null +++ b/Capy64/LuaRuntime/Constants.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime; + +public class Constants +{ + public const int MULTRET = -1; +} diff --git a/Capy64/LuaRuntime/Extensions/Libraries.cs b/Capy64/LuaRuntime/Extensions/Libraries.cs new file mode 100644 index 0000000..90071af --- /dev/null +++ b/Capy64/LuaRuntime/Extensions/Libraries.cs @@ -0,0 +1,57 @@ +using KeraLua; + +namespace Capy64.LuaRuntime.Extensions +{ + static class LuaExtensions + { + public static void OpenBase(this Lua lua) + { + lua.RequireF("_G", NativeLibraries.luaopen_base, true); + } + + public static void OpenCoroutine(this Lua lua) + { + lua.RequireF("coroutine", NativeLibraries.luaopen_coroutine, true); + } + + public static void OpenDebug(this Lua lua) + { + lua.RequireF("debug", NativeLibraries.luaopen_debug, true); + } + + public static void OpenIO(this Lua lua) + { + lua.RequireF("io", NativeLibraries.luaopen_io, true); + } + + public static void OpenMath(this Lua lua) + { + lua.RequireF("math", NativeLibraries.luaopen_math, true); + } + + public static void OpenOS(this Lua lua) + { + lua.RequireF("os", NativeLibraries.luaopen_os, true); + } + + public static void OpenPackage(this Lua lua) + { + lua.RequireF("package", NativeLibraries.luaopen_package, true); + } + + public static void OpenString(this Lua lua) + { + lua.RequireF("string", NativeLibraries.luaopen_string, true); + } + + public static void OpenTable(this Lua lua) + { + lua.RequireF("table", NativeLibraries.luaopen_table, true); + } + + public static void OpenUTF8(this Lua lua) + { + lua.RequireF("utf8", NativeLibraries.luaopen_utf8, true); + } + } +} diff --git a/Capy64/LuaRuntime/Extensions/NativeLibraries.cs b/Capy64/LuaRuntime/Extensions/NativeLibraries.cs new file mode 100644 index 0000000..ff30b78 --- /dev/null +++ b/Capy64/LuaRuntime/Extensions/NativeLibraries.cs @@ -0,0 +1,60 @@ +using System; +using System.Runtime.InteropServices; + +namespace Capy64.LuaRuntime.Extensions +{ + internal static partial class NativeLibraries + { +#if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__ + private const string LuaLibraryName = "@rpath/liblua54.framework/liblua54"; +#elif __ANDROID__ + private const string LuaLibraryName = "liblua54.so"; +#elif __MACOS__ + private const string LuaLibraryName = "liblua54.dylib"; +#elif WINDOWS_UWP + private const string LuaLibraryName = "lua54.dll"; +#else + private const string LuaLibraryName = "lua54"; +#endif + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_base(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_coroutine(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_debug(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_io(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_math(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_os(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_package(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_string(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_table(IntPtr luaState); + + [LibraryImport(LuaLibraryName)] + [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + internal static partial int luaopen_utf8(IntPtr luaState); + } +} diff --git a/Capy64/LuaRuntime/Extensions/Utils.cs b/Capy64/LuaRuntime/Extensions/Utils.cs new file mode 100644 index 0000000..1c5d558 --- /dev/null +++ b/Capy64/LuaRuntime/Extensions/Utils.cs @@ -0,0 +1,102 @@ +using KeraLua; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime.Extensions; + +public static class Utils +{ + public static void PushArray(this Lua state, object obj) + { + var iterable = obj as IEnumerable; + + state.NewTable(); + long i = 1; + foreach (var item in iterable) + { + state.PushValue(item); + state.RawSetInteger(-2, i++); + } + state.SetTop(-1); + } + + public static int PushValue(this Lua state, object? obj) + { + var type = obj.GetType(); + switch (obj) + { + case string str: + state.PushString(str); + break; + + case char: + state.PushString(obj.ToString()); + break; + + case byte: + case sbyte: + case short: + case ushort: + case int: + case uint: + case double: + state.PushNumber(Convert.ToDouble(obj)); + break; + + case long l: + state.PushInteger(l); + break; + + case bool b: + state.PushBoolean(b); + break; + + case null: + state.PushNil(); + break; + + case byte[] b: + state.PushBuffer(b); + break; + + case LuaFunction func: + state.PushCFunction(func); + break; + + case IntPtr ptr: + state.PushLightUserData(ptr); + break; + + default: + if (type.IsArray) + { + state.PushArray(obj); + } + else + { + throw new Exception("Invalid type provided"); + } + break; + + } + return 1; + } + + public static void PushManagedObject(this Lua state, T obj) + { + var type = obj.GetType(); + var members = type.GetMembers().Where(m => m.MemberType == MemberTypes.Method); + state.CreateTable(0, members.Count()); + foreach (var m in members) + { + state.PushCFunction(L => (int)type.InvokeMember(m.Name, BindingFlags.InvokeMethod, null, obj, new object[] { L })); + state.SetField(-2, m.Name); + } + } +} diff --git a/Capy64/LuaRuntime/Libraries/FileSystem.cs b/Capy64/LuaRuntime/Libraries/FileSystem.cs new file mode 100644 index 0000000..9d60f5b --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/FileSystem.cs @@ -0,0 +1,740 @@ +using Capy64.API; +using Capy64.LuaRuntime.Extensions; +using KeraLua; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Capy64.LuaRuntime.Libraries; + +public class FileSystem : IPlugin +{ + public static string BasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "Capy64"); + public static string DataPath = Path.Combine(BasePath, "data"); + + public FileSystem() + { + if (!Directory.Exists(DataPath)) + { + Directory.CreateDirectory(DataPath); + } + } + + // functions to add to the library, always end libraries with null + private LuaRegister[] FsLib = new LuaRegister[] { + new() + { + name = "list", + function = L_List, + }, + new() + { + name = "combine", + function = L_Combine, + }, + new() + { + name = "getName", + function = L_GetPathName, + }, + new() + { + name = "getDir", + function = L_GetDirectoryName, + }, + new() + { + name = "getSize", + function = L_GetFileSize, + }, + new() + { + name = "exists", + function = L_Exists, + }, + new() + { + name = "isReadOnly", + function = L_IsReadOnly, + }, + new() + { + name = "makeDir", + function = L_MakeDir, + }, + new() + { + name = "move", + function = L_Move, + }, + new() + { + name = "copy", + function = L_Copy, + }, + new() + { + name = "delete", + function = L_Delete, + }, + new() + { + name = "getAttributes", + function = L_GetAttributes, + }, + new() + { + name = "open", + function = L_Open, + }, + new(), // NULL + }; + + public void LuaInit(Lua state) + { + // Add "fs" library to lua, not global (uses require()) + state.RequireF("fs", Open, false); + } + + private int Open(IntPtr state) + { + var l = Lua.FromIntPtr(state); + l.NewLib(FsLib); + return 1; + } + + public static string SanitizePath(string path) + { + // Replace \ to / for cross compatibility in case users prefer to use \ + path = path.Replace("\\", "/"); + + // Get drive root (C:\ for Windows, / for *nix) + var rootPath = Path.GetFullPath(Path.GetPathRoot("/") ?? "/"); + + // Join path to rootPath and resolves to absolute path + // Relative paths are resolved here (es. ../ and ./) + var absolutePath = Path.GetFullPath(path, rootPath); + + // Trim root from path + return absolutePath.Remove(0, rootPath.Length); + } + + public static string Resolve(string path) + { + var isolatedPath = SanitizePath(path); + + // Now join the isolatedPath to the Lua directory, always inside of it + var resolvedPath = Path.Join(DataPath, isolatedPath); + + return resolvedPath; + } + + public static string CleanOutputPath(string path) + { + if (string.IsNullOrEmpty(path)) + return ""; + + var clean = path.Replace(Path.DirectorySeparatorChar, '/'); + return clean; + } + + public static string TrimBasePath(string path) + { + return path; + } + + public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, bool overwrite = false) + { + var dir = new DirectoryInfo(sourceDir); + + if (!dir.Exists) + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + + DirectoryInfo[] dirs = dir.GetDirectories(); + + Directory.CreateDirectory(destinationDir); + + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath, overwrite); + } + + if (recursive) + { + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite); + } + } + } + + private static int L_List(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = L.CheckString(1); + path = Resolve(path); + + if (!Directory.Exists(path)) + { + L.Error("directory not found"); + } + + var fileList = Directory.EnumerateFileSystemEntries(path); + + var list = new List(); + foreach (var file in fileList) + { + var sfile = Path.GetFileName(file); + list.Add(CleanOutputPath(sfile)); + } + + L.PushArray(list.Order().ToArray()); + + return 1; + } + + private static int L_Combine(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var nargs = L.GetTop(); + + var parts = new List(); + for (int i = 1; i <= nargs; i++) + { + var pathPart = L.CheckString(i); + parts.Add(pathPart); + } + + var result = Path.Join(parts.ToArray()); + if (string.IsNullOrEmpty(result)) + { + L.PushString(""); + return 1; + } + + result = SanitizePath(result); + L.PushString(CleanOutputPath(result)); + + return 1; + } + + private static int L_GetPathName(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = L.CheckString(1); + + var result = Path.GetFileName(path); + + L.PushString(CleanOutputPath(result)); + + return 1; + } + + private static int L_GetDirectoryName(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = L.CheckString(1); + + var result = Path.GetDirectoryName(path); + + L.PushString(CleanOutputPath(result)); + + return 1; + } + + private static int L_GetFileSize(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = L.CheckString(1); + + path = Resolve(path); + + if (!File.Exists(path)) + { + L.Error("file not found"); + } + + var fileInfo = new FileInfo(path); + L.PushInteger(fileInfo.Length); + + return 1; + } + + private static int L_Exists(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = L.CheckString(1); + var exists = Path.Exists(Resolve(path)); + + L.PushBoolean(exists); + + return 1; + } + + private static int L_IsReadOnly(IntPtr state) + { + var L = Lua.FromIntPtr(state); + var path = Resolve(L.CheckString(1)); + + if (File.Exists(path)) + { + var fileInfo = new FileInfo(path); + L.PushBoolean(fileInfo.IsReadOnly); + } + else if (Directory.Exists(path)) + { + var dirInfo = new DirectoryInfo(path); + var isReadOnly = dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly); + L.PushBoolean(isReadOnly); + } + else + { + L.Error("path not found"); + } + + return 1; + } + + private static int L_MakeDir(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = Resolve(L.CheckString(1)); + + if (Path.Exists(path)) + { + L.Error("path already exists"); + } + + Directory.CreateDirectory(path); + + return 0; + } + + private static int L_Move(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var sourcePath = Resolve(L.CheckString(1)); + var destPath = Resolve(L.CheckString(2)); + + if (!Path.Exists(sourcePath)) + { + L.Error("source path not found"); + } + + if (Path.Exists(destPath)) + { + L.Error("destination path already exists"); + } + + var attr = File.GetAttributes(sourcePath); + if (attr.HasFlag(FileAttributes.Directory)) + { + Directory.Move(sourcePath, destPath); + } + else + { + File.Move(sourcePath, destPath); + } + + return 0; + } + + private static int L_Copy(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var sourcePath = Resolve(L.CheckString(1)); + var destPath = Resolve(L.CheckString(2)); + + if (!Path.Exists(sourcePath)) + { + L.Error("source path not found"); + } + + if (Path.Exists(destPath)) + { + L.Error("destination path already exists"); + } + + var attr = File.GetAttributes(sourcePath); + if (attr.HasFlag(FileAttributes.Directory)) + { + CopyDirectory(sourcePath, destPath, true); + } + else + { + File.Copy(sourcePath, destPath); + } + + return 0; + } + + private static int L_Delete(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = Resolve(L.CheckString(1)); + bool recursive = false; + if (!L.IsNoneOrNil(2)) + { + L.CheckType(2, LuaType.Boolean); + recursive = L.ToBoolean(2); + } + + if (!Path.Exists(path)) + { + L.Error("path not found"); + } + + var attr = File.GetAttributes(path); + if (attr.HasFlag(FileAttributes.Directory)) + { + Directory.Delete(path, recursive); + } + else + { + File.Delete(path); + } + + return 0; + } + + private static int L_GetAttributes(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var path = Resolve(L.CheckString(1)); + + // { size = number, isDir = boolean, isReadOnly = boolean, created = number, modified = number } + if (!Path.Exists(path)) + { + L.Error("path not found"); + } + + var attributes = new Dictionary(); + + var pathAttributes = File.GetAttributes(path); + if (pathAttributes.HasFlag(FileAttributes.Directory)) + { + var dattrs = new DirectoryInfo(path); + attributes["size"] = 0; + attributes["isDirectory"] = true; + attributes["isReadOnly"] = dattrs.Attributes.HasFlag(FileAttributes.ReadOnly); + attributes["created"] = new DateTimeOffset(dattrs.CreationTimeUtc).ToUnixTimeMilliseconds(); + attributes["modified"] = new DateTimeOffset(dattrs.LastWriteTimeUtc).ToUnixTimeMilliseconds(); + } + else + { + var fattrs = new FileInfo(path); + attributes["size"] = fattrs.Length; + attributes["isDirectory"] = false; + attributes["isReadOnly"] = fattrs.IsReadOnly; + attributes["created"] = new DateTimeOffset(fattrs.CreationTimeUtc).ToUnixTimeMilliseconds(); + attributes["modified"] = new DateTimeOffset(fattrs.LastWriteTimeUtc).ToUnixTimeMilliseconds(); + } + + L.NewTable(); + + foreach (var attribute in attributes) + { + L.PushString(attribute.Key); + L.PushValue(attribute.Value); + + L.SetTable(-3); + } + + return 1; + } + + private static int L_Open(IntPtr state) + { + var L = Lua.FromIntPtr(state); + var path = Resolve(L.CheckString(1)); + var mode = L.CheckString(2); + + + var errorMessage = "invalid file mode"; + if (mode.Length < 1 && mode.Length > 2) + { + L.ArgumentError(2, errorMessage); + return 0; + } + + FileMode fileMode; + FileAccess fileAccess; + switch (mode[0]) + { + case 'r': + if (!File.Exists(path)) + { + L.Error("file not found"); + return 0; + } + fileMode = FileMode.Open; + fileAccess = FileAccess.Read; + break; + case 'w': + fileMode = FileMode.CreateNew; + fileAccess = FileAccess.Write; + break; + case 'a': + fileMode = FileMode.Append; + fileAccess = FileAccess.Write; + break; + default: + L.ArgumentError(2, errorMessage); + return 0; + } + + bool binaryMode = false; + if (mode.Length == 2) + { + if (mode[1] == 'b') + { + binaryMode = true; + } + else + { + L.ArgumentError(2, errorMessage); + return 0; + } + } + + var handle = File.Open(path, fileMode, fileAccess, FileShare.ReadWrite); + bool isClosed = false; + + + var functions = new Dictionary>(); + if (fileAccess == FileAccess.Read) + { + var reader = new StreamReader(handle); + functions["read"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + var count = (int)L.OptNumber(1, 1); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + if (reader.EndOfStream) + { + L.PushNil(); + return 1; + } + + if (binaryMode) + { + var data = new byte[count]; + handle.Read(data, 0, count); + L.PushBuffer(data); + } + else + { + var data = new char[count]; + reader.ReadBlock(data, 0, count); + L.PushString(new string(data)); + } + + return 1; + }; + + functions["readLine"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + var count = (int)L.OptNumber(1, 1); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + if (reader.EndOfStream) + { + L.PushNil(); + return 1; + } + + if (binaryMode) + { + var line = reader.ReadLine(); + L.PushBuffer(Encoding.UTF8.GetBytes(line)); + } + else + { + var line = reader.ReadLine(); + L.PushString(line); + } + + return 1; + }; + + functions["readAll"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + var count = (int)L.OptNumber(1, 1); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + if (reader.EndOfStream) + { + L.PushNil(); + return 1; + } + + if (binaryMode) + { + var content = reader.ReadToEnd(); + L.PushBuffer(Encoding.UTF8.GetBytes(content)); + } + else + { + var content = reader.ReadToEnd(); + L.PushString(content); + } + + return 1; + }; + + } + else + { + var writer = new StreamWriter(handle); + functions["write"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + L.ArgumentCheck(L.IsStringOrNumber(1), 1, "string or number value expected"); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + if (L.IsString(1)) + { + if (binaryMode) + { + handle.Write(L.ToBuffer(1)); + } + else + { + writer.Write(L.ToString(1)); + } + } + else + { + handle.WriteByte((byte)L.ToInteger(1)); + } + + return 0; + }; + + functions["writeLine"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + L.ArgumentCheck(L.IsStringOrNumber(1), 1, "string or number value expected"); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + if (L.IsString(1)) + { + if (binaryMode) + { + handle.Write(L.ToBuffer(1)); + handle.WriteByte((byte)'\n'); + } + else + { + writer.WriteLine(L.ToString(1)); + } + } + else + { + handle.WriteByte((byte)L.ToInteger(1)); + handle.WriteByte((byte)'\n'); + } + + return 0; + }; + + functions["flush"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + handle.Flush(); + + return 0; + }; + } + + + functions["close"] = (IntPtr state) => + { + var L = Lua.FromIntPtr(state); + + if (isClosed) + { + L.Error("file handle is closed"); + return 0; + } + + if (fileAccess != FileAccess.Read) + { + handle.Flush(); + } + handle.Dispose(); + + isClosed = true; + + return 0; + }; + + + L.NewTable(); + foreach (var pair in functions) + { + L.PushString(pair.Key); + L.PushCFunction(new LuaFunction(pair.Value)); + L.SetTable(-3); + } + + return 1; + } + +} diff --git a/Capy64/LuaRuntime/Libraries/Graphics.cs b/Capy64/LuaRuntime/Libraries/Graphics.cs new file mode 100644 index 0000000..8ba51b7 --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/Graphics.cs @@ -0,0 +1,93 @@ +using Capy64.API; +using Capy64.Core; +using KeraLua; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; + +namespace Capy64.LuaRuntime.Libraries; + +public class Graphics : IPlugin +{ + private static IGame _game; + public Graphics(IGame game) + { + _game = game; + } + + private LuaRegister[] GfxLib = new LuaRegister[] { + new() + { + name = "drawPoint", + function = L_DrawPoint, + }, + new() + { + name = "drawPoints", + function = L_DrawPoints, + }, + new(), // NULL + }; + + public void LuaInit(Lua state) + { + state.RequireF("graphics", Open, false); + } + + public int Open(IntPtr state) + { + var l = Lua.FromIntPtr(state); + l.NewLib(GfxLib); + return 1; + } + + private static int L_DrawPoint(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var x = L.CheckInteger(1) - 1; + var y = L.CheckInteger(2) - 1; + var c = L.CheckInteger(3); + var s = L.OptInteger(4, 1); + + Utils.UnpackRGB((uint)c, out var r, out var g, out var b); + _game.Drawing.DrawPoint(new(x, y), new Color(r, g, b), (int)s); + + return 0; + } + + private static int L_DrawPoints(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.CheckType(1, LuaType.Table); + var c = L.CheckInteger(2); + + L.SetTop(1); + + int size = (int)L.Length(1); + + L.ArgumentCheck(size % 2 == 0, 1, "expected an even table"); + + List pts = new(); + for (int i = 1; i <= size; i += 2) + { + L.GetInteger(1, i); + L.ArgumentCheck(L.IsNumber(-1), 1, "expected number at index " + i); + var x = (int)L.ToNumber(-1) - 1; + L.Pop(1); + + L.GetInteger(1, i + 1); + L.ArgumentCheck(L.IsNumber(-1), 1, "expected number at index " + (i + 1)); + var y = (int)L.ToNumber(-1) - 1; + L.Pop(1); + + pts.Add(new Point(x, y)); + } + + Utils.UnpackRGB((uint)c, out var r, out var g, out var b); + _game.Drawing.Plot(pts, new(r, g, b)); + + return 0; + } +} diff --git a/Capy64/LuaRuntime/Libraries/HTTP.cs b/Capy64/LuaRuntime/Libraries/HTTP.cs new file mode 100644 index 0000000..3073bea --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/HTTP.cs @@ -0,0 +1,22 @@ +using Capy64.API; +using KeraLua; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime.Libraries; + +public class HTTP : IPlugin +{ + private static IGame _game; + public HTTP(IGame game) + { + _game = game; + } + + public void LuaInit(Lua state) + { + } +} diff --git a/Capy64/LuaRuntime/Libraries/OS.cs b/Capy64/LuaRuntime/Libraries/OS.cs new file mode 100644 index 0000000..9ae59ea --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/OS.cs @@ -0,0 +1,34 @@ +using Capy64.API; +using KeraLua; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime.Libraries; + +public class OS : IPlugin +{ + private static IGame _game; + public OS(IGame game) + { + _game = game; + } + + + public void LuaInit(Lua state) + { + state.GetGlobal("os"); + state.PushString("shutdown"); + state.PushCFunction(L_Shutdown); + state.SetTable(-3); + } + + private static int L_Shutdown(IntPtr state) + { + BIOS.Bios.Shutdown(); + + return 0; + } +} diff --git a/Capy64/LuaRuntime/Libraries/Screen.cs b/Capy64/LuaRuntime/Libraries/Screen.cs new file mode 100644 index 0000000..48f56df --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/Screen.cs @@ -0,0 +1,104 @@ +using Capy64.API; +using Capy64.Core; +using KeraLua; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime.Libraries; + +public class Screen : IPlugin +{ + private static IGame _game; + public Screen(IGame game) + { + _game = game; + } + + private LuaRegister[] ScreenLib = new LuaRegister[] { + new() + { + name = "getSize", + function = L_GetSize, + }, + new() + { + name = "setSize", + function = L_SetSize, + }, + new() + { + name = "getScale", + function = L_GetScale, + }, + new() + { + name = "setScale", + function = L_SetScale, + }, + new(), // NULL + }; + + public void LuaInit(Lua state) + { + state.RequireF("screen", Open, false); + } + + public int Open(IntPtr state) + { + var l = Lua.FromIntPtr(state); + l.NewLib(ScreenLib); + return 1; + } + + private static int L_GetSize(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushInteger(_game.Width); + L.PushInteger(_game.Height); + + return 2; + } + + private static int L_SetSize(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var w = L.CheckInteger(1); + var h = L.CheckInteger(2); + + _game.Width = (int)w; + _game.Height = (int)h; + + _game.UpdateSize(); + + return 0; + } + + private static int L_GetScale(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushNumber(_game.Scale); + + return 1; + } + + private static int L_SetScale(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var s = L.CheckNumber(1); + + _game.Scale = (float)s; + + _game.UpdateSize(); + + return 0; + } + +} diff --git a/Capy64/LuaRuntime/Libraries/Term.cs b/Capy64/LuaRuntime/Libraries/Term.cs new file mode 100644 index 0000000..396bceb --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/Term.cs @@ -0,0 +1,574 @@ +using Capy64.API; +using Capy64.Eventing.Events; +using KeraLua; +using Microsoft.Xna.Framework; +using System; + +namespace Capy64.LuaRuntime.Libraries; + +internal class Term : IPlugin +{ + private struct Char + { + public char Character; + public Color Foreground; + public Color Background; + } + + public const int CharWidth = 7; + public const int CharHeight = 13; + + public const int CursorDelay = 30; + + public static int Width { get; private set; } = 57; + public static int Height { get; private set; } = 23; + public static int RealWidth => CharWidth * Width; + public static int RealHeight => CharHeight * Height; + public static Vector2 CharOffset => new(1, 0); + public static Vector2 CursorPosition => _cursorPosition + Vector2.One; + private static Vector2 _cursorPosition { get; set; } + public static Color ForegroundColor { get; set; } + public static Color BackgroundColor { get; set; } + private static Char?[] CharGrid; + + private static IGame _game; + private static bool cursorState = false; + private static bool enableCursor = true; + public Term(IGame game) + { + _game = game; + + _cursorPosition = Vector2.Zero; + ForegroundColor = Color.White; + BackgroundColor = Color.Black; + + UpdateSize(); + + _game.EventEmitter.OnTick += OnTick; + } + + private LuaRegister[] TermLib = new LuaRegister[] + { + new() + { + name = "write", + function = L_Write, + }, + new() + { + name = "getPos", + function = L_GetPos, + }, + new() + { + name = "setPos", + function = L_SetPos, + }, + new() + { + name = "getSize", + function = L_GetSize, + }, + new() + { + name = "setSize", + function = L_SetSize, + }, + new() + { + name = "getForeground", + function = L_GetForegroundColor, + }, + new() + { + name = "setForeground", + function = L_SetForegroundColor, + }, + new() + { + name = "getBackground", + function = L_GetBackgroundColor, + }, + new() + { + name = "setBackground", + function = L_SetBackgroundColor, + }, + new() + { + name = "toRealPos", + function = L_ToReal, + }, + new() + { + name = "fromRealPos", + function = L_FromReal, + }, + new() + { + name = "clear", + function = L_Clear, + }, + new() + { + name = "clearLine", + function = L_ClearLine, + }, + new() + { + name = "scroll", + function = L_Scroll, + }, + new() + { + name = "getBlink", + function = L_GetBlink, + }, + new() + { + name = "setBlink", + function = L_SetBlink, + }, + new(), + }; + + public void LuaInit(Lua state) + { + state.RequireF("term", Open, false); + } + + public int Open(IntPtr state) + { + var l = Lua.FromIntPtr(state); + l.NewLib(TermLib); + return 1; + } + + public static void UpdateSize() + { + _game.Width = RealWidth; + _game.Height = RealHeight; + _game.UpdateSize(); + CharGrid = new Char?[Width * Height]; + } + + public static Vector2 ToRealPos(Vector2 termPos) + { + return new(termPos.X * CharWidth, termPos.Y * CharHeight); + } + + public static void PlotChar(Vector2 pos, char ch, Color? fgc = null, Color? bgc = null, bool save = true) + { + if (pos.X < 0 || pos.Y < 0 || pos.X >= Width || pos.Y >= Height) + return; + + var fg = fgc ?? ForegroundColor; + var bg = bgc ?? BackgroundColor; + + var realpos = ToRealPos(pos); + var charpos = realpos + CharOffset; + _game.Drawing.DrawRectangle(realpos, new(CharWidth, CharHeight), bg, Math.Min(CharWidth, CharHeight)); + _game.Drawing.DrawString(charpos, ch.ToString(), fg); + + if (!save) + return; + + CharGrid[(int)pos.X + (int)pos.Y * Width] = new Char + { + Character = ch, + Foreground = ForegroundColor, + Background = BackgroundColor, + }; + } + + public static void RedrawPos(Vector2 pos) + { + if (pos.X < 0 || pos.Y < 0 || pos.X >= Width || pos.Y >= Height) + return; + + var ch = CharGrid[(int)pos.X + (int)pos.Y * Width] ?? + new Char + { + Character = ' ', + Foreground = ForegroundColor, + Background = BackgroundColor, + }; + + PlotChar(pos, ch.Character, ch.Foreground, ch.Background, false); + } + + public static void RedrawAll() + { + for(int y = 0; y < Height; y++) + { + for(int x = 0; x < Width; x++) + { + RedrawPos(new(x, y)); + } + } + } + + public static void DumpScreen(bool clear = false) + { + if(clear) + Console.Clear(); + Console.WriteLine("\n /{0}", new string('-', Width)); + for (int i = 0; i < CharGrid.Length; i++) + { + if (i % Width == 0) + { + if (i > 0) + Console.Write("|\n"); + Console.Write("{0,3}:", i / Width); + } + Console.Write(CharGrid[i]?.Character ?? ' '); + + } + Console.WriteLine("|\n \\{0}", new string('-', Width)); + } + + private void OnTick(object sender, TickEvent e) + { + if (((int)e.TotalTicks % CursorDelay) == 0) + { + cursorState = !cursorState; + UpdateCursor(); + } + } + + private static void UpdateCursor() + { + if (!enableCursor) + return; + + if (_cursorPosition.X < 0 || _cursorPosition.Y < 0 || _cursorPosition.X >= Width || _cursorPosition.Y >= Height) + return; + + var ch = CharGrid[(int)_cursorPosition.X + (int)_cursorPosition.Y * Width] ?? + new Char + { + Character = ' ', + Foreground = ForegroundColor, + Background = BackgroundColor, + }; + + Color fg, bg; + + if (cursorState) + { + fg = ch.Background; + bg = ch.Foreground; + } + else + { + fg = ch.Foreground; + bg = ch.Background; + } + + PlotChar(_cursorPosition, ch.Character, fg, bg, false); + } + + private static void ClearGrid() + { + CharGrid = new Char?[CharGrid.Length]; + _game.Drawing.Clear(BackgroundColor); + } + + public static void Write(string text) + { + foreach (var ch in text) + { + PlotChar(_cursorPosition, ch); + _cursorPosition = new(_cursorPosition.X + 1, _cursorPosition.Y); + } + } + + private static int L_Write(IntPtr state) + { + var L = Lua.FromIntPtr(state); + var str = L.ToString(1); + + Write(str); + + return 0; + } + + public static void SetCursorPosition(int x, int y) + { + RedrawPos(_cursorPosition); + cursorState = true; + _cursorPosition = new(x - 1, y - 1); + UpdateCursor(); + } + + private static int L_GetPos(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushInteger((int)_cursorPosition.X + 1); + L.PushInteger((int)_cursorPosition.Y + 1); + + return 2; + } + + private static int L_SetPos(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var x = L.CheckNumber(1); + var y = L.CheckNumber(2); + + SetCursorPosition((int)x, (int)y); + + return 0; + } + + public static void SetSize(int width, int height) + { + Width = width; + Height = height; + + UpdateSize(); + } + + private static int L_GetSize(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushInteger(Width); + L.PushInteger(Height); + + return 2; + } + + private static int L_SetSize(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var w = (int)L.CheckNumber(1); + var h = (int)L.CheckNumber(2); + + if (w <= 0) + { + L.ArgumentError(1, "number must be greater than 0"); + } + if (h <= 0) + { + L.ArgumentError(2, "number must be greater than 0"); + } + + SetSize(w, h); + + return 0; + } + + private static int L_GetForegroundColor(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushInteger(ForegroundColor.R); + L.PushInteger(ForegroundColor.G); + L.PushInteger(ForegroundColor.B); + + return 3; + } + + private static int L_SetForegroundColor(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var argsn = L.GetTop(); + + byte r, g, b; + // R, G, and B values + if (argsn == 3) + { + r = (byte)L.CheckNumber(1); + g = (byte)L.CheckNumber(2); + b = (byte)L.CheckNumber(3); + } + // packed RGB value + else if (argsn == 1) + { + var c = (uint)L.CheckInteger(1); + Utils.UnpackRGB(c, out r, out g, out b); + } + else + { + L.ArgumentError(argsn, "expected 1 or 3 number values"); + return 0; + } + + ForegroundColor = new(r, g, b); + + return 0; + } + + private static int L_GetBackgroundColor(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushInteger(BackgroundColor.R); + L.PushInteger(BackgroundColor.G); + L.PushInteger(BackgroundColor.B); + + return 3; + } + + private static int L_SetBackgroundColor(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var argsn = L.GetTop(); + + byte r, g, b; + // R, G, and B values + if (argsn == 3) + { + r = (byte)L.CheckNumber(1); + g = (byte)L.CheckNumber(2); + b = (byte)L.CheckNumber(3); + } + // packed RGB value + else if (argsn == 1) + { + var c = (uint)L.CheckInteger(1); + Utils.UnpackRGB(c, out r, out g, out b); + } + else + { + L.ArgumentError(argsn, "expected 1 or 3 number values"); + return 0; + } + + BackgroundColor = new(r, g, b); + + return 0; + } + + private static int L_ToReal(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var x = (int)L.CheckNumber(1); + var y = (int)L.CheckNumber(2); + + L.PushInteger(x * CharWidth); + L.PushInteger(y * CharHeight); + + return 2; + } + + private static int L_FromReal(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var x = (int)L.CheckNumber(1); + var y = (int)L.CheckNumber(2); + + L.PushInteger(x / CharWidth); + L.PushInteger(y / CharHeight); + + return 2; + } + + public static void Clear() + { + ClearGrid(); + + RedrawAll(); + } + + private static int L_Clear(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + Clear(); + + return 0; + } + + public static void ClearLine() + { + for (int x = 0; x < Width; x++) + PlotChar(new(x, _cursorPosition.Y), ' '); + } + + private static int L_ClearLine(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + ClearLine(); + + return 0; + } + + public static void Scroll(int lines) + { + if (lines == 0) + return; + + if (lines <= -Height || lines >= Height) + { + ClearGrid(); + return; + } + + var lineLength = Math.Abs(lines) * Width; + var newGrid = new Char?[CharGrid.Length]; + if (lines < 0) + { + Array.Copy(CharGrid, lineLength, newGrid, 0, CharGrid.Length - lineLength); + } + else + { + Array.Copy(CharGrid, 0, newGrid, lineLength, CharGrid.Length - lineLength); + } + + CharGrid = newGrid; + + RedrawAll(); + } + + private static int L_Scroll(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var lines = L.CheckInteger(1) * -1; + + Scroll((int)lines); + + return 0; + } + + public static void SetCursorBlink(bool enable) + { + enableCursor = enable; + + if (!enableCursor) + { + RedrawPos(_cursorPosition); + } + } + + private static int L_GetBlink(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.PushBoolean(enableCursor); + + return 1; + } + + private static int L_SetBlink(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + L.CheckType(1, LuaType.Boolean); + var enableCursor = L.ToBoolean(1); + + SetCursorBlink(enableCursor); + + return 0; + } +} diff --git a/Capy64/LuaRuntime/Libraries/Timer.cs b/Capy64/LuaRuntime/Libraries/Timer.cs new file mode 100644 index 0000000..c39aaa7 --- /dev/null +++ b/Capy64/LuaRuntime/Libraries/Timer.cs @@ -0,0 +1,77 @@ +using Capy64.API; +using KeraLua; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime.Libraries; + +class Timer : IPlugin +{ + private LuaRegister[] TimerLib = new LuaRegister[] + { + new() + { + name = "start", + function = L_StartTimer, + }, + + new(), + }; + + private static IGame _game; + private static uint _timerId; + public Timer(IGame game) + { + _game = game; + _timerId = 0; + } + + public void LuaInit(Lua state) + { + state.RequireF("timer", Open, false); + } + + private int Open(IntPtr state) + { + var l = Lua.FromIntPtr(state); + l.NewLib(TimerLib); + return 1; + } + + private static int L_StartTimer(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var delay = L.CheckInteger(1); + L.ArgumentCheck(delay > 0, 1, "delay must be greater than 0"); + + var timerId = _timerId++; + var timer = new System.Timers.Timer + { + AutoReset = false, + Enabled = true, + Interval = delay, + }; + + timer.Elapsed += (o, e) => + { + _game.LuaRuntime.PushEvent("timer", timerId); + }; + + L.PushInteger(timerId); + return 1; + } + + private static int L_Sleep(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + + + return 0; + } +} diff --git a/Capy64/LuaRuntime/LuaEvent.cs b/Capy64/LuaRuntime/LuaEvent.cs new file mode 100644 index 0000000..caf1ea3 --- /dev/null +++ b/Capy64/LuaRuntime/LuaEvent.cs @@ -0,0 +1,8 @@ +namespace Capy64.LuaRuntime; + +public struct LuaEvent +{ + public string Name { get; set; } + public object[] Parameters { get; set; } + public bool BypassFilter { get; set; } +} diff --git a/Capy64/LuaRuntime/LuaException.cs b/Capy64/LuaRuntime/LuaException.cs new file mode 100644 index 0000000..6afa5d7 --- /dev/null +++ b/Capy64/LuaRuntime/LuaException.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime; + +public class LuaException : Exception +{ + public LuaException() + { + } + + public LuaException(string message) : base(message) + { + } + + public LuaException(string message, Exception innerException) : base(message, innerException) + { + } + + protected LuaException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +} diff --git a/Capy64/LuaRuntime/Runtime.cs b/Capy64/LuaRuntime/Runtime.cs new file mode 100644 index 0000000..76aa801 --- /dev/null +++ b/Capy64/LuaRuntime/Runtime.cs @@ -0,0 +1,137 @@ +using Capy64.LuaRuntime.Extensions; +using Capy64.LuaRuntime.Libraries; +using KeraLua; +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; + +namespace Capy64.LuaRuntime; + +public class Runtime +{ + private readonly ConcurrentQueue eventQueue = new(new LuaEvent[] + { + new() + { + Name = "init", + Parameters = Array.Empty() + } + }); + + private readonly Lua Parent; + public Lua Thread { get; private set; } + private string[] filters = Array.Empty(); + private bool Disposing = false; + public Runtime() + { + Parent = new(false) + { + Encoding = Encoding.UTF8, + }; + + Sandbox.OpenLibraries(Parent); + + Thread = Parent.NewThread(); + + } + + public void Patch() + { + Sandbox.Patch(Parent); + } + + public void Init() + { + var initContent = File.ReadAllText(Path.Combine(FileSystem.DataPath, "init.lua")); + var status = Thread.LoadString(initContent, "=init.lua"); + if (status != LuaStatus.OK) + { + throw new LuaException(Thread.ToString(-1)); + } + } + + public void PushEvent(LuaEvent ev) + { + eventQueue.Enqueue(ev); + } + + public void PushEvent(string name, params object[] pars) + { + eventQueue.Enqueue(new() { Name = name, Parameters = pars }); + } + + /// + /// Resume the Lua thread + /// + /// Whether it yielded + public bool Resume() + { + while (eventQueue.TryDequeue(out LuaEvent ev)) + { + if (!ResumeThread(ev)) + return false; + } + + return true; + } + + private bool ResumeThread(LuaEvent ev) + { + + + if (filters.Length > 0 && !filters.Contains(ev.Name)) + { + if (!ev.BypassFilter) + { + return true; + } + } + + filters = Array.Empty(); + var evpars = PushEventToStack(ev); + if (Disposing) + return false; + var status = Thread.Resume(null, evpars, out int pars); + if (status is LuaStatus.OK or LuaStatus.Yield) + { + if(Disposing) + return false; + filters = new string[pars]; + for (int i = 0; i < pars; i++) + { + filters[i] = Thread.OptString(i + 1, null); + } + Thread.Pop(pars); + return status == LuaStatus.Yield; + } + + var error = Thread.OptString(-1, "Unknown exception"); + Thread.Traceback(Thread); + var stacktrace = Thread.OptString(-1, ""); + + throw new LuaException($"Top thread exception:\n{error}\n{stacktrace}"); + } + + private int PushEventToStack(LuaEvent ev) + { + Thread.PushString(ev.Name); + if (ev.Parameters != null) + { + foreach (var par in ev.Parameters) + { + Thread.PushValue(par); + } + } + return (ev.Parameters?.Length ?? 0) + 1; + } + + public void Close() + { + Disposing = true; + Parent.Close(); + } +} diff --git a/Capy64/LuaRuntime/Sandbox.cs b/Capy64/LuaRuntime/Sandbox.cs new file mode 100644 index 0000000..84b3223 --- /dev/null +++ b/Capy64/LuaRuntime/Sandbox.cs @@ -0,0 +1,304 @@ +using Capy64.LuaRuntime.Extensions; +using Capy64.LuaRuntime.Libraries; +using KeraLua; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.LuaRuntime; + +internal class Sandbox +{ + private struct CallData + { + public int Delta; + } + internal static void OpenLibraries(Lua L) + { + L.OpenBase(); + L.OpenCoroutine(); + L.OpenDebug(); + L.OpenMath(); + L.OpenString(); + L.OpenTable(); + L.OpenUTF8(); + L.OpenOS(); + + L.OpenPackage(); + } + internal static void Patch(Lua L) + { + // patch package + L.GetGlobal("package"); + + // package.cpatch: empty paths + L.PushString("cpath"); + L.PushString(""); + L.SetTable(-3); + + // package.path: local paths only + L.PushString("path"); + L.PushString("./?.lua;./?/init.lua"); + L.SetTable(-3); + + // package.loadlib: remove func to load C libs + L.PushString("loadlib"); + L.PushNil(); + L.SetTable(-3); + + L.PushString("searchpath"); + L.PushCFunction(L_PatchedSearchpath); + L.SetTable(-3); + + // package.config: apply unix directory separator + L.PushString("config"); + L.GetTable(-2); + var packageConfig = L.ToString(-1); + packageConfig = string.Concat("/", packageConfig.AsSpan(1)); + L.PushString("config"); + L.PushString(packageConfig); + L.SetTable(-4); + + // delete 3 and 4 searchers + L.PushString("searchers"); + L.GetTable(-3); + + L.PushNil(); + L.SetInteger(-2, 3); + L.PushNil(); + L.SetInteger(-2, 4); + + // replace searcher 2 with a custom sandboxed one + L.PushCFunction(L_Searcher); + L.SetInteger(-2, 2); + + L.Pop(L.GetTop()); + + // Replace loadfile with sandboxed one + L.PushCFunction(L_Loadfile); + L.SetGlobal("loadfile"); + + // Replace dofile with sandboxed one + L.PushCFunction(L_Dofile); + L.SetGlobal("dofile"); + + L.Pop(L.GetTop()); + + // yeet dangerous os functions + L.GetGlobal("os"); + + L.PushString("execute"); + L.PushNil(); + L.SetTable(-3); + + L.PushString("tmpname"); + L.PushNil(); + L.SetTable(-3); + + L.PushString("remove"); + L.PushNil(); + L.SetTable(-3); + + L.PushString("rename"); + L.PushNil(); + L.SetTable(-3); + + L.PushString("getenv"); + L.PushNil(); + L.SetTable(-3); + } + + internal static int L_Searcher(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var libname = L.CheckString(1); + + L.GetGlobal("package"); + + // get searcher path as arg for package.searchpath + L.PushString("path"); + L.GetTable(-2); + var searchpath = L.ToString(-1); + + // get package.searchpath + L.PushString("searchpath"); + L.GetTable(-3); + + L.PushString(libname); + L.PushString(searchpath); + + L.Call(2, 2); + + if (L.IsNil(-2)) + { + return 1; + } + + // path resolved by package.searchpath + var foundPath = L.ToString(-2); + + L.GetGlobal("loadfile"); + L.PushString(foundPath); + L.Call(1, 2); + + if (L.IsNil(-2)) + { + var errorMessage = new StringBuilder(); + errorMessage.AppendFormat("error loading module '{0}' from file '{1}':", libname, foundPath); + errorMessage.Append('\t'); + errorMessage.AppendLine(L.ToString(-1)); + + L.PushString(errorMessage.ToString()); + + return 1; + } + + L.Remove(-1); + + L.PushString(foundPath); + + return 2; + } + + internal static int L_PatchedSearchpath(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var libname = L.CheckString(1); + var searchpath = L.CheckString(2); + var possiblePaths = searchpath + .Split(';') + .Select(p => p.Replace("?", libname)); + + var errorMessage = new StringBuilder(); + foreach (var possiblePath in possiblePaths) + { + var path = FileSystem.Resolve(possiblePath); + var info = new FileInfo(path); + if (!info.Exists) + { + errorMessage.AppendLine(string.Format("no file '{0}'", possiblePath)); + continue; + } + + try + { + File.Open(path, FileMode.Open, FileAccess.Read).Dispose(); + } + catch + { + errorMessage.AppendLine(string.Format("error opening file '{0}'", possiblePath)); + continue; + } + + L.PushString(possiblePath); + return 1; + } + + L.PushNil(); + L.PushString(errorMessage.ToString()); + + return 2; + } + + internal static int L_Loadfile(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var filename = L.CheckString(1); + + bool hasMode = !L.IsNone(2); + bool hasEnv = !L.IsNone(3); + + var path = FileSystem.Resolve(filename); + + var fileInfo = new FileInfo(path); + if (!fileInfo.Exists) + { + L.PushNil(); + L.PushString($"cannot open {filename}: No such file or directory"); + return 2; + } + else if (fileInfo.Attributes.HasFlag(FileAttributes.Directory)) + { + L.PushNil(); + L.PushString($"cannot read {filename}: Is a directory"); + return 2; + } + + var chunk = File.ReadAllBytes(path); + + L.GetGlobal("load"); + L.PushBuffer(chunk); + L.PushString(filename); + + var values = 2; + + if (hasMode) + { + L.PushCopy(2); + values++; + } + + if (hasEnv) + { + L.PushCopy(3); + values++; + } + + L.Call(values, 2); + + return 2; + } + + internal static int LK_Dofile(IntPtr state, int status, IntPtr ctx) + { + var L = Lua.FromIntPtr(state); + + var nargs = L.GetTop(); + + return nargs; + } + + internal static int L_Dofile(IntPtr state) + { + var L = Lua.FromIntPtr(state); + + var filename = L.CheckString(1); + + L.GetGlobal("loadfile"); + L.PushString(filename); + + L.Call(1, 2); + + if (L.IsNil(-2)) + { + L.Error(L.ToString(-1)); + return 0; + } + + L.PushCopy(-2); + L.Insert(1); + L.SetTop(1); + + L.CallK(0, Constants.MULTRET, 0, LK_Dofile); + return 0; + } + + internal static void DumpStack(Lua state) + { + var n = state.GetTop(); + for (int i = n; i >= 1; i--) + { + Console.WriteLine("{0,4}: {1}", -i, state.ToString(-i)); + } + Console.WriteLine("--------------"); + } + +} \ No newline at end of file diff --git a/Capy64/PluginManager/PluginLoadContext.cs b/Capy64/PluginManager/PluginLoadContext.cs new file mode 100644 index 0000000..e571602 --- /dev/null +++ b/Capy64/PluginManager/PluginLoadContext.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64.PluginManager +{ + class PluginLoadContext : AssemblyLoadContext + { + private AssemblyDependencyResolver _resolver; + + public PluginLoadContext(string pluginPath) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } + } +} diff --git a/Capy64/PluginManager/PluginLoader.cs b/Capy64/PluginManager/PluginLoader.cs new file mode 100644 index 0000000..4f75319 --- /dev/null +++ b/Capy64/PluginManager/PluginLoader.cs @@ -0,0 +1,44 @@ +using Capy64.API; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Capy64.PluginManager; + +internal class PluginLoader +{ + private static Assembly LoadPlugin(string fileName) + { + var path = Path.Combine(Environment.CurrentDirectory, fileName); + + var loadContext = new PluginLoadContext(path); + return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path))); + } + + public static List LoadAllPlugins(string pluginsPath, IServiceProvider provider) + { + if (!Directory.Exists(pluginsPath)) + Directory.CreateDirectory(pluginsPath); + + var plugins = new List(); + foreach (var fileName in Directory.GetFiles(pluginsPath).Where(q => q.EndsWith(".dll"))) + { + var assembly = LoadPlugin(fileName); + + foreach (Type type in assembly.GetTypes()) + { + if (typeof(IPlugin).IsAssignableFrom(type)) + { + IPlugin result = ActivatorUtilities.CreateInstance(provider, type) as IPlugin; + plugins.Add(result); + } + } + + } + + return plugins; + } +} diff --git a/Capy64/Program.cs b/Capy64/Program.cs new file mode 100644 index 0000000..02ca9f5 --- /dev/null +++ b/Capy64/Program.cs @@ -0,0 +1,15 @@ +using Capy64; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; + +using var game = new Capy64.Capy64(); +using IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.AddSingleton(game); + services.AddHostedService(); + }) + .Build(); + +await host.RunAsync(); \ No newline at end of file diff --git a/Capy64/Properties/launchSettings.json b/Capy64/Properties/launchSettings.json new file mode 100644 index 0000000..d11e4a2 --- /dev/null +++ b/Capy64/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "WSL": { + "commandName": "WSL2", + "distributionName": "" + }, + "Capy64": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Capy64/Utils.cs b/Capy64/Utils.cs new file mode 100644 index 0000000..a3b546f --- /dev/null +++ b/Capy64/Utils.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Capy64; + +public static class Utils +{ + public struct Borders + { + public int Top, Bottom, Left, Right; + } + public static void UnpackRGB(uint packed, out byte r, out byte g, out byte b) + { + b = (byte)(packed & 0xff); + g = (byte)(packed >> 8 & 0xff); + r = (byte)(packed >> 16 & 0xff); + } +} diff --git a/Capy64/Worker.cs b/Capy64/Worker.cs new file mode 100644 index 0000000..18a583d --- /dev/null +++ b/Capy64/Worker.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Capy64; + +public class Worker : IHostedService +{ + private readonly IGame _game; + private readonly IHostApplicationLifetime _appLifetime; + private readonly IServiceProvider _serviceProvider; + + public Worker(IGame game, IHostApplicationLifetime appLifetime, IServiceProvider serviceProvider) + { + _game = game; + _appLifetime = appLifetime; + _serviceProvider = serviceProvider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _appLifetime.ApplicationStarted.Register(OnStarted); + _appLifetime.ApplicationStopping.Register(OnStopping); + _appLifetime.ApplicationStopped.Register(OnStopped); + + _game.Exiting += OnGameExiting; + + _game.ConfigureServices(_serviceProvider); + + return Task.CompletedTask; + } + + private void OnGameExiting(object sender, EventArgs e) + { + StopAsync(new CancellationToken()); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _appLifetime.StopApplication(); + + return Task.CompletedTask; + } + + private void OnStarted() + { + _game.Run(); + } + + private void OnStopping() + { + } + + private void OnStopped() + { + } +} \ No newline at end of file diff --git a/Capy64/app.manifest b/Capy64/app.manifest new file mode 100644 index 0000000..35dbe91 --- /dev/null +++ b/Capy64/app.manifest @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + +