mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-01-18 10:36: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