mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-01-18 02:26:44 +00:00
Add project files.
This commit is contained in:
commit
84cba789a3
55 changed files with 7077 additions and 0 deletions
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
|
@ -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
|
363
.gitignore
vendored
Normal file
363
.gitignore
vendored
Normal file
|
@ -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
|
25
Capy64.sln
Normal file
25
Capy64.sln
Normal file
|
@ -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
|
36
Capy64/.config/dotnet-tools.json
Normal file
36
Capy64/.config/dotnet-tools.json
Normal file
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
16
Capy64/API/IPlugin.cs
Normal file
16
Capy64/API/IPlugin.cs
Normal file
|
@ -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) { }
|
||||
|
||||
}
|
446
Capy64/Assets/Lua/init.lua
Normal file
446
Capy64/Assets/Lua/init.lua
Normal file
|
@ -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
|
93
Capy64/Assets/Lua/lib/colors.lua
Normal file
93
Capy64/Assets/Lua/lib/colors.lua
Normal file
|
@ -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;
|
15
Capy64/Assets/Lua/lib/event.lua
Normal file
15
Capy64/Assets/Lua/lib/event.lua
Normal file
|
@ -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
|
123
Capy64/Assets/Lua/lib/expect.lua
Normal file
123
Capy64/Assets/Lua/lib/expect.lua
Normal file
|
@ -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 })
|
1762
Capy64/Assets/Lua/lib/utfstring.lua
Normal file
1762
Capy64/Assets/Lua/lib/utfstring.lua
Normal file
File diff suppressed because it is too large
Load diff
122
Capy64/Assets/bios.lua
Normal file
122
Capy64/Assets/bios.lua
Normal file
|
@ -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
|
BIN
Capy64/Assets/font.ttf
Normal file
BIN
Capy64/Assets/font.ttf
Normal file
Binary file not shown.
180
Capy64/BIOS/Bios.cs
Normal file
180
Capy64/BIOS/Bios.cs
Normal file
|
@ -0,0 +1,180 @@
|
|||
using Capy64.API;
|
||||
using Capy64.Core;
|
||||
using Capy64.Eventing;
|
||||
using Capy64.Eventing.Events;
|
||||
using Capy64.LuaRuntime;
|
||||
using Capy64.LuaRuntime.Libraries;
|
||||
using KeraLua;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Capy64.BIOS;
|
||||
|
||||
public class Bios : IPlugin
|
||||
{
|
||||
private static IGame _game;
|
||||
private EventEmitter _eventEmitter;
|
||||
private RuntimeInputEvents _runtimeInputEvents;
|
||||
private Drawing _drawing;
|
||||
private bool CloseRuntime = false;
|
||||
|
||||
public Bios(IGame game)
|
||||
{
|
||||
_game = game;
|
||||
_eventEmitter = game.EventEmitter;
|
||||
_drawing = game.Drawing;
|
||||
_game.EventEmitter.OnInit += OnInit;
|
||||
_eventEmitter.OnTick += OnTick;
|
||||
}
|
||||
|
||||
private void OnInit(object sender, EventArgs e)
|
||||
{
|
||||
RunBIOS();
|
||||
}
|
||||
|
||||
private void OnTick(object sender, TickEvent e)
|
||||
{
|
||||
if (CloseRuntime)
|
||||
{
|
||||
_runtimeInputEvents.Unregister();
|
||||
|
||||
_game.LuaRuntime.Close();
|
||||
|
||||
CloseRuntime = false;
|
||||
|
||||
StartLuaOS();
|
||||
}
|
||||
|
||||
Resume();
|
||||
}
|
||||
|
||||
private void RunBIOS()
|
||||
{
|
||||
_game.LuaRuntime = new();
|
||||
InitLuaPlugins();
|
||||
|
||||
_game.LuaRuntime.Thread.PushCFunction(L_OpenDataFolder);
|
||||
_game.LuaRuntime.Thread.SetGlobal("openDataFolder");
|
||||
|
||||
_game.LuaRuntime.Thread.PushCFunction(L_InstallOS);
|
||||
_game.LuaRuntime.Thread.SetGlobal("installOS");
|
||||
|
||||
_game.LuaRuntime.Thread.PushCFunction(L_Exit);
|
||||
_game.LuaRuntime.Thread.SetGlobal("exit");
|
||||
|
||||
|
||||
var status = _game.LuaRuntime.Thread.LoadFile("Assets/bios.lua");
|
||||
if (status != LuaStatus.OK)
|
||||
{
|
||||
throw new LuaException(_game.LuaRuntime.Thread.ToString(-1));
|
||||
}
|
||||
|
||||
_runtimeInputEvents = new RuntimeInputEvents(_eventEmitter, _game.LuaRuntime);
|
||||
_runtimeInputEvents.Register();
|
||||
}
|
||||
|
||||
private void StartLuaOS()
|
||||
{
|
||||
InstallOS();
|
||||
|
||||
try
|
||||
{
|
||||
_game.LuaRuntime = new Runtime();
|
||||
InitLuaPlugins();
|
||||
_game.LuaRuntime.Patch();
|
||||
_game.LuaRuntime.Init();
|
||||
_runtimeInputEvents = new(_eventEmitter, _game.LuaRuntime);
|
||||
_runtimeInputEvents.Register();
|
||||
|
||||
}
|
||||
catch (LuaException ex)
|
||||
{
|
||||
var panic = new PanicScreen(_game.Drawing);
|
||||
_drawing.Begin();
|
||||
panic.Render("Cannot load operating system!", ex.Message);
|
||||
_drawing.End();
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
try
|
||||
{
|
||||
var yielded = _game.LuaRuntime.Resume();
|
||||
if (!yielded)
|
||||
{
|
||||
_game.Exit();
|
||||
}
|
||||
}
|
||||
catch (LuaException e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
var panic = new PanicScreen(_game.Drawing);
|
||||
panic.Render(e.Message);
|
||||
_runtimeInputEvents.Unregister();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitLuaPlugins()
|
||||
{
|
||||
var allPlugins = new List<IPlugin>(_game.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;
|
||||
}
|
||||
}
|
61
Capy64/BIOS/PanicScreen.cs
Normal file
61
Capy64/BIOS/PanicScreen.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
127
Capy64/BIOS/RuntimeInputEvents.cs
Normal file
127
Capy64/BIOS/RuntimeInputEvents.cs
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
171
Capy64/Capy64.cs
Normal file
171
Capy64/Capy64.cs
Normal file
|
@ -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<IPlugin> NativePlugins { get; private set; }
|
||||
public IList<IPlugin> 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<IPlugin> 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<IPlugin>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
46
Capy64/Capy64.csproj
Normal file
46
Capy64/Capy64.csproj
Normal file
|
@ -0,0 +1,46 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RollForward>Major</RollForward>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\font.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Assets\Lua/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Icon.ico" />
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8" />
|
||||
<PackageReference Include="KeraLua" Version="1.3.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
|
||||
<PackageReference Include="MonoGame.Extended.Graphics" Version="3.8.0" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Assets\bios.lua">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
|
||||
<Message Text="Restoring dotnet tools" Importance="High" />
|
||||
<Exec Command="dotnet tool restore" />
|
||||
</Target>
|
||||
</Project>
|
15
Capy64/Content/Content.mgcb
Normal file
15
Capy64/Content/Content.mgcb
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
#----------------------------- Global Properties ----------------------------#
|
||||
|
||||
/outputDir:bin/$(Platform)
|
||||
/intermediateDir:obj/$(Platform)
|
||||
/platform:DesktopGL
|
||||
/config:
|
||||
/profile:Reach
|
||||
/compress:False
|
||||
|
||||
#-------------------------------- References --------------------------------#
|
||||
|
||||
|
||||
#---------------------------------- Content ---------------------------------#
|
||||
|
73
Capy64/Core/Audio.cs
Normal file
73
Capy64/Core/Audio.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
176
Capy64/Core/Drawing.cs
Normal file
176
Capy64/Core/Drawing.cs
Normal file
|
@ -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<Point> 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<Point> 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();
|
||||
}
|
||||
}
|
109
Capy64/Eventing/EventEmitter.cs
Normal file
109
Capy64/Eventing/EventEmitter.cs
Normal file
|
@ -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<MouseButtonEvent> OnMouseDown;
|
||||
public event EventHandler<MouseButtonEvent> OnMouseUp;
|
||||
public event EventHandler<MouseMoveEvent> OnMouseMove;
|
||||
public event EventHandler<MouseWheelEvent> OnMouseWheel;
|
||||
|
||||
// Keyboard events
|
||||
public event EventHandler<KeyEvent> OnKeyDown;
|
||||
public event EventHandler<KeyEvent> OnKeyUp;
|
||||
public event EventHandler<CharEvent> OnChar;
|
||||
|
||||
// Functional events
|
||||
public event EventHandler<TickEvent> 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);
|
||||
}
|
||||
}
|
||||
}
|
8
Capy64/Eventing/Events/CharEvent.cs
Normal file
8
Capy64/Eventing/Events/CharEvent.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace Capy64.Eventing.Events;
|
||||
|
||||
public class CharEvent : EventArgs
|
||||
{
|
||||
public char Character { get; set; }
|
||||
}
|
14
Capy64/Eventing/Events/KeyEvent.cs
Normal file
14
Capy64/Eventing/Events/KeyEvent.cs
Normal file
|
@ -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; }
|
||||
}
|
13
Capy64/Eventing/Events/MouseButtonEvent.cs
Normal file
13
Capy64/Eventing/Events/MouseButtonEvent.cs
Normal file
|
@ -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; }
|
||||
}
|
10
Capy64/Eventing/Events/MouseMoveEvent.cs
Normal file
10
Capy64/Eventing/Events/MouseMoveEvent.cs
Normal file
|
@ -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; }
|
||||
}
|
11
Capy64/Eventing/Events/MouseWheelEvent.cs
Normal file
11
Capy64/Eventing/Events/MouseWheelEvent.cs
Normal file
|
@ -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; }
|
||||
}
|
14
Capy64/Eventing/Events/TickEvent.cs
Normal file
14
Capy64/Eventing/Events/TickEvent.cs
Normal file
|
@ -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; }
|
||||
}
|
282
Capy64/Eventing/InputManager.cs
Normal file
282
Capy64/Eventing/InputManager.cs
Normal file
|
@ -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<MouseButton, ButtonState> 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<Keys> 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();
|
||||
}
|
||||
}
|
21
Capy64/Extensions/Bindings/SDL2.cs
Normal file
21
Capy64/Extensions/Bindings/SDL2.cs
Normal file
|
@ -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);
|
||||
}
|
62
Capy64/Extensions/GameWindowExtensions.cs
Normal file
62
Capy64/Extensions/GameWindowExtensions.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="https://github.com/flibitijibibo/SDL2-CS/blob/master/src/SDL2.cs#L1530"/>
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
31
Capy64/IGame.cs
Normal file
31
Capy64/IGame.cs
Normal file
|
@ -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<IPlugin> NativePlugins { get; }
|
||||
IList<IPlugin> 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<EventArgs> Exiting;
|
||||
void Run();
|
||||
void Exit();
|
||||
}
|
BIN
Capy64/Icon.bmp
Normal file
BIN
Capy64/Icon.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 256 KiB |
BIN
Capy64/Icon.ico
Normal file
BIN
Capy64/Icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 144 KiB |
12
Capy64/LuaRuntime/Constants.cs
Normal file
12
Capy64/LuaRuntime/Constants.cs
Normal file
|
@ -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;
|
||||
}
|
57
Capy64/LuaRuntime/Extensions/Libraries.cs
Normal file
57
Capy64/LuaRuntime/Extensions/Libraries.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
60
Capy64/LuaRuntime/Extensions/NativeLibraries.cs
Normal file
60
Capy64/LuaRuntime/Extensions/NativeLibraries.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
102
Capy64/LuaRuntime/Extensions/Utils.cs
Normal file
102
Capy64/LuaRuntime/Extensions/Utils.cs
Normal file
|
@ -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<T>(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);
|
||||
}
|
||||
}
|
||||
}
|
740
Capy64/LuaRuntime/Libraries/FileSystem.cs
Normal file
740
Capy64/LuaRuntime/Libraries/FileSystem.cs
Normal file
|
@ -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<string>();
|
||||
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<string>();
|
||||
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<string, object>();
|
||||
|
||||
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<string, Func<nint, int>>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
93
Capy64/LuaRuntime/Libraries/Graphics.cs
Normal file
93
Capy64/LuaRuntime/Libraries/Graphics.cs
Normal file
|
@ -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<Point> 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;
|
||||
}
|
||||
}
|
22
Capy64/LuaRuntime/Libraries/HTTP.cs
Normal file
22
Capy64/LuaRuntime/Libraries/HTTP.cs
Normal file
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
34
Capy64/LuaRuntime/Libraries/OS.cs
Normal file
34
Capy64/LuaRuntime/Libraries/OS.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
104
Capy64/LuaRuntime/Libraries/Screen.cs
Normal file
104
Capy64/LuaRuntime/Libraries/Screen.cs
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
574
Capy64/LuaRuntime/Libraries/Term.cs
Normal file
574
Capy64/LuaRuntime/Libraries/Term.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
77
Capy64/LuaRuntime/Libraries/Timer.cs
Normal file
77
Capy64/LuaRuntime/Libraries/Timer.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
8
Capy64/LuaRuntime/LuaEvent.cs
Normal file
8
Capy64/LuaRuntime/LuaEvent.cs
Normal file
|
@ -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; }
|
||||
}
|
27
Capy64/LuaRuntime/LuaException.cs
Normal file
27
Capy64/LuaRuntime/LuaException.cs
Normal file
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
137
Capy64/LuaRuntime/Runtime.cs
Normal file
137
Capy64/LuaRuntime/Runtime.cs
Normal file
|
@ -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<LuaEvent> eventQueue = new(new LuaEvent[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "init",
|
||||
Parameters = Array.Empty<object>()
|
||||
}
|
||||
});
|
||||
|
||||
private readonly Lua Parent;
|
||||
public Lua Thread { get; private set; }
|
||||
private string[] filters = Array.Empty<string>();
|
||||
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 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume the Lua thread
|
||||
/// </summary>
|
||||
/// <returns>Whether it yielded</returns>
|
||||
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<string>();
|
||||
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();
|
||||
}
|
||||
}
|
304
Capy64/LuaRuntime/Sandbox.cs
Normal file
304
Capy64/LuaRuntime/Sandbox.cs
Normal file
|
@ -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("--------------");
|
||||
}
|
||||
|
||||
}
|
42
Capy64/PluginManager/PluginLoadContext.cs
Normal file
42
Capy64/PluginManager/PluginLoadContext.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
44
Capy64/PluginManager/PluginLoader.cs
Normal file
44
Capy64/PluginManager/PluginLoader.cs
Normal file
|
@ -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<IPlugin> LoadAllPlugins(string pluginsPath, IServiceProvider provider)
|
||||
{
|
||||
if (!Directory.Exists(pluginsPath))
|
||||
Directory.CreateDirectory(pluginsPath);
|
||||
|
||||
var plugins = new List<IPlugin>();
|
||||
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;
|
||||
}
|
||||
}
|
15
Capy64/Program.cs
Normal file
15
Capy64/Program.cs
Normal file
|
@ -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<IGame>(game);
|
||||
services.AddHostedService<Worker>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync();
|
11
Capy64/Properties/launchSettings.json
Normal file
11
Capy64/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"profiles": {
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"distributionName": ""
|
||||
},
|
||||
"Capy64": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
21
Capy64/Utils.cs
Normal file
21
Capy64/Utils.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
62
Capy64/Worker.cs
Normal file
62
Capy64/Worker.cs
Normal file
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
43
Capy64/app.manifest
Normal file
43
Capy64/app.manifest
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Capy64"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on and is
|
||||
is designed to work with. Uncomment the appropriate elements and Windows will
|
||||
automatically selected the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
</assembly>
|
Loading…
Reference in a new issue