1
0
Fork 0
mirror of https://github.com/ctruLua/ctruLua.git synced 2025-10-27 16:39:29 +00:00

Compare commits

..

48 commits

Author SHA1 Message Date
9a97ad0bca Update links before they break 2021-06-27 14:27:16 +02:00
Firew0lf
f118baa37c README rewrite 2016-06-27 20:16:38 +02:00
Reuh
5888ee3810 Fixed UDP sockets, added documentation, cleaning
udp:receivefrom arguments and return value changed so it actually works.
2016-06-27 19:31:07 +02:00
Reuh
3303e9783d Fix documentation 2016-06-27 15:38:26 +02:00
Reuh
b47971bfca Added rectangle gradients, triangles, updated sf2dlib
Gradients are done with additional parameters to gfx.rectangle, so old code that gave the function too many parameters and expected them to be ignored may break.
2016-06-27 15:29:55 +02:00
Reuh
4a2c1a7c68 Updated ctr.apt to the latest ctrulib, added ctr.apt.isNew3DS() 2016-06-27 13:42:38 +02:00
Firew0lf
20f8cd3cb9 Added "ctr.hid.cstick()", untested with the circle pad pro, works on New 3DS.
I'm working on the issue with :bind(), looks like it's not just my code ...
2016-06-06 19:17:47 +02:00
Firew0lf
5494f3d2e5 Added some sleep mode related functions, fixed the example, fixed some things 2016-05-09 23:28:41 +02:00
Reuh
b4ceb200ea Added gfx.linedRectangle and gfx.linedCircle 2016-04-29 17:55:59 +02:00
Firew0lf
707b1a451e Added the ctr.uds lib (3DS-to-3DS wireless communication), Added a function to add ssl certificates for HTTP contexts, Minor documentation update.
The UDS lib is completely untested, because I only have 1 3DS.
2016-04-26 14:44:18 +02:00
Reuh
f554f53a47 ctr.time() returns a negative value; updated documentation and sprite.lua 2016-04-25 21:02:32 +02:00
Reuh
2504fc90f1 Merge branch 'master' of https://github.com/ctruLua/ctruLua 2016-04-25 19:43:38 +02:00
Reuh
d0fb704205 Added hotspot arguments when drawing rotated textures
Did related changes to sprite.lua and cleaned stuff.

I had to add a function to sf2dlib to make this work, so a make build-sf2dlib is required. Also, we should probably send this change to the original sf2dlib repository...
2016-04-25 19:43:09 +02:00
Reuh
ac47b1d981 Added missing documentation in texture.load 2016-04-24 18:21:21 +02:00
Reuh
4d1e3ec455 Improved map:draw a lot, fixed GC issues with ctr.map, changed some things internally
map:draw should be faster and more flexible.
2016-04-22 16:45:56 +02:00
Reuh
358b68c643 Renamed item.fileSize to item.size in fs.list() return value, enabled 3D in the editor, added a lot of missing documentation
For the return values, I followed this rule: if the fuction returns true on success, it should return false, error on error; for every other case it should return nil, error on error.

Also, who doesn't want to edit code in 3D ? The line depth depends on the indentation level.
2016-04-22 13:42:59 +02:00
Reuh
2b7d37304d Made the editor usable
It was able to edit file before, but didn't show the changes on the screen, so this was easy to do.

Also renamed some setTextSize->setSize missed in last commit.
2016-04-21 22:31:48 +02:00
Reuh
347e480d5a Moved ctr.gfx.set|getTextSize to ctr.gfx.font.set|getSize, added missing documentation to font:width
Started working again on the editor
2016-04-21 21:51:45 +02:00
Firew0lf
b798818e99 Fixed the ROMFS, Removed some fields in fs.list(), Updated sftdlib
fs.list() now returns a table with tables containing the fields "name", "fileSize", and "isDirectory"
2016-04-16 13:24:03 +02:00
Firew0lf
6b65df0b8e Fixed some documentation, added some things to sockets, "Fixed" the ROMFS
The romfs is still not working, but it works better.
2016-04-13 16:19:25 +02:00
Firew0lf
e7ff54d58c Added SSL support to sockets, Added console/stdout support, Removed apt.init/apt.shutdown, Added some SSL options to httpc
The console can __not__ be used with gfx.start() on a screen. You don't have to gfx.render() while print()ing, but you should do it when you are in the main loop.
The SSL sockets don't work with Citra.
2016-04-10 01:47:52 +02:00
Reuh
e87651a404 Readd require('ctr') in keyboard 2016-04-02 19:09:40 +02:00
Reuh
15bb00780b Fixed sublime-completions doc building 2016-04-02 19:03:24 +02:00
Neil Zeke Cecchini
1f23b86379 Changed the icon, and deleted the test files.
The new one is much nicer. As for the test files, they had nothing to do
here from the start.
2016-04-02 18:41:25 +02:00
Neil Zeke Cecchini
05c9adc2a0 Updated apps that rely on file selection
The editor doesn't use openfile.lua anymore, the main shell is updated
to use filepicker.filePicker's new format. Also, updated the editor to
use resources/VeraMono.ttf instead of its own copy, which was deleted.
2016-04-02 18:41:25 +02:00
Neil Zeke Cecchini
4669a68402 Updated filepicker.lua and adding documentation
Effectively rendering openfile.lua completely obsolete, which is why it
was deleted. filepicker.lua is now way smarter, handles file creation
and has a documentation file in LDoc.
2016-04-02 18:41:25 +02:00
Neil Zeke Cecchini
c687efcfb4 Made keyboard.lua use a configuration file
Thus removing the need to edit it directly as an user
2016-04-02 18:41:25 +02:00
Firew0lf
eae356ce80 Added the :save() method to textures, Added support for some image formats
The new image formats are loaded with stb_image, and the textures are saved with stb_image_write.
https://github.com/nothings/stb
You can now load GIFs (not animated), PSD, TGA, HDR, PIC and PNM.
The texture loading still uses sfil for JPEG, PNG, and BMP. You can force the use of stbi by loading with
the "type" argument in texture.load() set to 242 (or anything 5<x<250, but 242 is a good value).
The :save() formats are PNG and BMP only.
2016-03-26 16:49:23 +01:00
Reuh
6143341760 Fixed audio looping when using streaming
Also made sure the chunk size is lower or equal the total file size.
2016-03-24 18:07:41 +01:00
Reuh
4c9bdf75fa Unload streaming data when stopping audio
Forgot some memory freeing, sorry.
2016-03-23 20:16:20 +01:00
Reuh
04eb578892 Updated audio example 2016-03-23 19:53:46 +01:00
Reuh
6ac83ca6df Added streaming support to ctr.audio
Currently runs in the main thread because I can't get it to work on another thread.

You will need to call audio.update() each frame to make audio streaming work.
2016-03-23 19:50:10 +01:00
Firew0lf
5107f0277c Updated sf2dlib, added a "thickness" argument to lines (warning: break some old lines), Added WIP render targets, Added arguments (main file and root path)
You'll need to update some of your codes if you used lines with colors, or you'll have some ... Nice line effects.
2016-03-22 21:48:52 +01:00
Neil Zeke Cecchini
694159f444 Updated LSH to use the new constants
Doesn't change much anyway.
2016-03-13 21:30:14 +01:00
Firew0lf
e5467b663d Added ctr.root, close #7, changed the max path length to 1024
ctr.root is not exactly the path to ctruLua.3dsx minus the file name, but is equivalent with HBL and $
(other launchers may work too, and it works perfectly with Citra), and is "romfs:/" if romfs is enabl$
2016-03-12 23:14:42 +01:00
Firew0lf
6a9fbbb133 Added ":addTrustedRootCA()" to the httpc contexts, close #8, Added some values in ctr.
I didn't test :addTrustedRootCA(), but it's just a simple string-to-char+size function.
2016-03-12 19:57:59 +01:00
Reuh
9db21c7831 Added gfx.scissor
Note: doesn't work on citra but it does on real hardware.
2016-03-09 16:13:06 +01:00
Reuh
acd41db805 Update README.md links 2016-03-09 13:30:15 +01:00
Reuh
34c48f360e Added a way to build the documentation as .sublime-completions files (make build-doc-st)
Also replaced make build-doc with make build-doc-html so we can easily add new documentation formats in the future.

To add ctrµLua API autocompletion to Sublime Text, simply copy the output directory (doc/sublimetext) to your ST's package directory.
2016-02-29 19:42:52 +01:00
Firew0lf
e84ab0e3b2 Added functions to set/get the default text size, closes #4 2016-02-24 21:48:28 +01:00
Reuh
f180d4352d Merge pull request #3 from ctruLua/lsh
Replacing the old shell with LSH
2016-02-24 19:07:06 +01:00
Neil Zeke Cecchini
34d12eae0f Added VeraMono.ttf, for real this time 2016-02-24 19:04:17 +01:00
Neil Zeke Cecchini
60b304b2d3 Forgot the font 2016-02-24 19:00:47 +01:00
Neil Zeke Cecchini
cbfc7bfaca Oopsie. 2016-02-24 18:01:37 +01:00
Neil Zeke Cecchini
b6beaddf66 Tabulation correction 2016-02-24 17:55:12 +01:00
Firew0lf
a380f09a34 Updated httpc.c to the latest ctrulib, fixed the cfgu example.
The name is still buggy, can't find why.
2016-02-24 14:46:11 +01:00
Neil Zeke Cecchini
3eb41b5062 Tidbits of documentation 2016-02-24 14:23:36 +01:00
Neil Zeke Cecchini
116575fb5f Replaced the old shell with LSH 2016-02-24 14:19:16 +01:00
59 changed files with 11546 additions and 966 deletions

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
/build/*
/ctruLua.*
/doc/html/*
/doc/html/*
/doc/sublimetext/*

View file

@ -38,6 +38,8 @@ APP_TITLE := ctruLua
APP_DESCRIPTION := Lua for the 3DS. Yes, it works.
APP_AUTHOR := Reuh, Firew0lf and NegiAD
ICON := icon.png
APP_VERSION := $(shell git describe --abbrev=0 --tags)
LASTCOMMIT := $(shell git rev-parse HEAD)
#---------------------------------------------------------------------------------
# options for code generation
@ -48,7 +50,10 @@ CFLAGS := -g -Wall -O2 -mword-relocations -std=gnu11 \
-fomit-frame-pointer -ffast-math \
$(ARCH)
CFLAGS += $(INCLUDE) -DARM11 -D_3DS
CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DCTR_VERSION=\"$(APP_VERSION)\" -DCTR_BUILD=\"$(LASTCOMMIT)\"
ifneq ($(ROMFS),)
CFLAGS += -DROMFS
endif
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
@ -65,7 +70,8 @@ LIBDIRS := $(CTRULIB) $(PORTLIBS) \
$(CURDIR)/libs/3ds_portlibs/build \
$(CURDIR)/libs/sf2dlib/libsf2d \
$(CURDIR)/libs/sftdlib/libsftd \
$(CURDIR)/libs/sfillib/libsfil
$(CURDIR)/libs/sfillib/libsfil \
$(CURDIR)/libs/stb
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
@ -129,7 +135,6 @@ endif
ifneq ($(ROMFS),)
export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS)
CFLAGS += -DROMFS
endif
.PHONY: $(BUILD) clean all
@ -166,8 +171,17 @@ build-all:
@make build
build-doc:
@echo Building HTML documentation...
@make build-doc-html
@echo Building SublimeText documentation...
@make build-doc-st
build-doc-html:
@cd doc/ && ldoc . && cd ..
build-doc-st:
@cd doc/ && ldoc . --template ./ --ext sublime-completions --dir ./sublimetext/ && cd ..
#---------------------------------------------------------------------------------
clean:
@rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf
@ -197,8 +211,17 @@ clean-all:
@make clean
clean-doc:
@echo Cleaning HTML documentation...
@make clean-doc-html
@echo Cleaning SublimeText documentation...
@make clean-doc-st
clean-doc-html:
@rm -rf doc/html
clean-doc-st:
@rm -rf doc/sublimetext
#---------------------------------------------------------------------------------
else

View file

@ -1,25 +1,86 @@
# ctrµLua
Everything is in the Wiki.
![banner](https://www.dropbox.com/s/cqmtoohyx6t7q7c/banner.png?raw=1)
Warning: the 'u' in the repo's name is a 'µ', not a 'u'.
#### Builds ![build status](http://thomas99.no-ip.org:3000/ctrulua.png)
### Users part
* Most recent working build: [here](http://thomas99.no-ip.org:3000/ctrulua/builds/latest/artifacts/ctruLua.3dsx) (warning: only tested with Citra, sometimes on real hardware).
* See http://thomas99.no-ip.org:3000/ctrulua for all the builds.
#### How to install
* Download ctruLua.zip from the [releases](https://github.com/ctruLua/ctruLua/releases) (for something stable) or the [CI server](https://reuh.eu/ctrulua/ci/ctrulua) (for more features)
* Unzip it on your SD card, in a folder inside your homebrews folder; if you use HBL, extract it in `/3ds/ctrulua/`
* Put some scripts wherever you want, just remember where
* Launch CtrµLua from your homebrew launcher
* Use the shell to run your scripts
### Homebrewers part
#### Builds ![build status](https://reuh.eu/ctrulua/ci/ctrulua.png)
* Most recent working build: [ctruLua.3dsx](https://reuh.eu/ctrulua/ci/ctrulua/builds/latest/artifacts/ctruLua.3dsx)
* See [https://reuh.eu/ctrulua/ci/ctrulua](https://reuh.eu/ctrulua/ci/ctrulua) for all the builds.
#### Hello world
```Lua
local ctr = require("ctr")
local gfx = require("ctr.gfx")
local hid = require("ctr.hid")
while ctr.run() do
hid.read()
local keys = hid.keys()
if keys.held.start then break end
gfx.start(gfx.TOP)
gfx.text(2, 2, "Hello, world !")
gfx.stop()
gfx.render()
end
```
This script will print "Hello, world !" on the top screen, and will exit if the user presses Start.
This is the "graphical" version; there's also a text-only version, based on the console:
```Lua
local ctr = require("ctr")
local gfx = require("ctr.gfx")
local hid = require("ctr.hid")
gfx.console()
print("Hello, world !")
while ctr.run() do
hid.read()
local keys = hid.keys()
if keys.held.start then break end
gfx.render()
end
gfx.disableConsole()
```
#### Lua API Documentation
* An online version of the documentation can be found [here](https://reuh.eu/ctrulua/latest/html/)
* To build the documentation, run `make build-doc-html` (requires [LDoc](https://github.com/stevedonovan/LDoc)).
### Developers part
#### Build instructions
* Setup your environment as shown here : http://3dbrew.org/wiki/Setting_up_Development_Environment
* Clone this repository and run the command `make build-all` to build all the dependencies.
* If you only made changes to ctrµLua, run `make` to rebuild ctµLua without rebuilding all the dependencies.
* If you only made changes to ctrµLua, run `make` to rebuild ctrµLua without rebuilding all the dependencies.
May not work under Windows.
#### Lua API Documentation
### Credits
* An online version of the documentation can be found here : http://thomas99.no-ip.org/ctrulua
* To build the documentation, run `make build-doc` (requires [LDoc](https://github.com/stevedonovan/LDoc)).
#### Based on ctrulib by smealum: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib)
* __Smealum__ and everyone who worked on the ctrulib: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib)
* __Xerpi__ for the [sf2dlib](https://github.com/xerpi/sf2dlib), [sftdlib](https://github.com/xerpi/sftdlib) and [sfillib](https://github.com/xerpi/sfillib)
* __All the [Citra](https://citra-emu.org/) developers__
* __Everyone who worked on [DevKitARM](http://devkitpro.org/)__
* __Nothings__ for the [stb](https://github.com/nothings/stb) libs
* Everyone who worked on the other libs we use

View file

@ -11,7 +11,7 @@ format = "markdown"
plain = true
-- Input files
topics = "../README.md"
topics = {"../README.md", "filepicker.md"}
file = "../source/"
examples = "../sdcard/3ds/ctruLua/examples/"
manual_url = "file://../libs/lua-5.3.1/doc/manual.html"

93
doc/filepicker.md Normal file
View file

@ -0,0 +1,93 @@
# filepicker
## filePicker([workingDirectory[, bindings[, callbacks[, ...]]]])
### Argument: workingDirectory
The directory that shows up first in the file browser.
If this is nil, ctr.fs.getDirectory() is used.
The recommended form is sdmc:/path/ or romfs:/path/, but it can be a simple /path/ instead.
#### Possible values
- string
- nil
### Argument: bindings
A table, list of filetypes and key bindings related to these filetypes.
#### Format
```
{
__default, __directory, [Lua regexp] = {
[keys from ctr.hid.keys()] = {
function
string
}...
__name = string
}...
}
```
The Lua regexp is matched against the filename to determine if it is of this type.
__directory is the "file type" for directories
__default is the "file type" for files that cannot be matched with any other.
Also, every other type inherits the values it doesn't define from __default.
A file type contains the human-readable name (__name) of the type, displayed on the bottom screen,
as well as an optional binding for each key.
The optional binding is formed of an anonymous function, followed by the key's label to be displayed on the bottom screen,
if the label is nil, the key isn't displayed but still bound.
The function is defined as-is:
##### function(externalConfig, selected, bindingPattern, bindingKey)
externalConfig.workingDirectory is the active directory for filePicker, doesn't necessarily match ctrµLua's.
externalConfig.bindings, externalConfig.callbacks and externalConfig.additionalArguments all are the arguments passed to filePicker,
starting from position 2.
externalConfig.fileList is the list of files currently displayed by filePicker, in the same format as is returned by ctr.fs.listDirectory().
selected.inList is the absolute position of the cursor in externalConfig.fileList
selected.offset is the number of items skipped for display from fileList
bindingPattern is the [Lua regexp] defined earlier, and bindingKey the [key] that triggered this event.
This function may return the same thing as filePicker itself (Defined later here), or nothing. If it returns nothing,
filePicker will keep running, if it returns the same returns as filePicker, filePicker will exit, returning these values.
#### Notes
Sane defaults are set if you did not set them otherwise:
__default.x quits filePicker, returning the current directory, "__directory", nil and "x". See the returns to understand what this means.
__directory.a changes directories to that directory.
### Argument: callbacks
A table defining the callbacks ran at the end of each of the equivalent phases.
#### Format
```
{
drawTop, drawBottom, eventHandler = function
}
```
All of these take the following parameters: (externalConfig, selected)
They have the meaning defined earlier.
#### Notes
Although drawTop and drawBottom are ran at the end of their respective functions,
eventHandler is not, as it cannot be without being run repeatedly, so it's run at the beginning of
the ACTUAL eventHandler instead of its end.
### Argument: ...
Additional parameters. All of these are aggregated orderly in externalConfig.additionalArguments and passed around as explained earlier.
They have no specific meaning unless defined so by event handling functions.
### Return 1: selectedPath
The path selected by the either, may or may not have the sdmc:/romfs: prefix, depending on your input.
A string.
### Return 2: bindingPattern
The pattern the file matched to. You can use this to know exactly which kind of file you're dealing with
A string.
### Return 3: mode
Included handlers may have it be "open", in case you're opening an existing file, "new" in case the user wants to create a new file,
Or nil. A "nil" is assumed to mean that the user didn't pick anything.
A string or nil.
### Return 4: key
The key that triggered the event that made filePicker exit.
A string.
## Included event handlers you have available are:
- filepicker.changeDirectory - The name is on the tin, change workingDirectory to the active element and refresh the file list for that path.
- filepicker.openFile - Quits and returns as described by this document based on the active element.
- filepicker.newFile - Prompts the user to input a file name manually, relative to the current working directory, and with FAT-incompatible characters excluded. Quits and returns that.
- filepicker.nothing - Do nothing and keep on running. Literally. This is used as a plug to enable an action for all (or a certain type) but files of a certain type (or more precise than the initial type).

118
doc/ldoc.ltp Normal file
View file

@ -0,0 +1,118 @@
# -- LDoc template by Reuh.
# -- Generates sublime-completions files which can be used in Sublime Text 3 for autocompletion.
# -- Based on the HTML template, so generated files will contain lots of comments with additionnal data not handled by ST.
# -- I tried to make the generated files human-readable, so they may be used instead of the HTML documentation.
# -- Typical usage: ldoc . --template ./ --ext sublime-completions --dir ./sublimetext/
#
# local scope = "source.lua"
# local function e(str) return (str or ""):gsub("\"", "\\\"") end -- escape json string ("str")
# local function indent(indentation, str) -- indent str (except first line) with indentation
# return (str or ""):gsub("(.-)\n", indentation.."%1\n"):gsub("\n([^\n]*)$", "\n"..indentation.."%1"):gsub("^"..indentation, "")
# end
# local function displayName(item) return item.type == "function" and item.name..item.args or item.name end -- nice name
# local function autocompleteName(item) -- ST-autocomplete name
# if item.type == "function" then
# local i = 1
# local args = "("
# for arg in (item.args:match("^%((.*)%)$")..","):gmatch("%s*([^,]+)%,") do
# args = args.."${"..i..":"..arg.."}, "
# i = i +1
# end
# return item.name..args:gsub("%, $", "")..")"
# else return item.name end
# end
/*
Title: $(ldoc.title)
Project: $(ldoc.project)
Description: $(ldoc.description)
# if ldoc.single then
(Single module-project)
# end
# if not module then
Project contents:
# for kind, mods in ldoc.kinds() do
$(kind)
# for m in mods() do
$(m.name): $(m.summary)
# end
# end
*/
# else -- if not module
*/
/*
Module: $(module.name)
Summary: $(module.summary)
Description: $(module.description)
Module contents:
# for kind, items in module.kinds() do
$(kind)
# for item in items() do
$(item.type) $(displayName(item))
# end
# end
*/
/* Completions */
{
"scope": "$(e(scope))",
"completions": [
# for kind, items in module.kinds() do
/* $(kind) */
# for item in items() do
/*
$(item.type) $(displayName(item))
Summary: $(item.summary)
Description: $(indent("\t\t\t", item.description))
# if item.type == "function" then
Parameters:
# for p in item.params:iter() do
# local default = item:default_of_param(p)
# if default == true then default = "(optional)"
# elseif default then default = "(defaults to "..default..")" end
($(item:type_of_param(p))) $(item:display_name_of(p)):$(item.params.map[p]) $(default)
# end
# local retgroups = item.retgroups or {}
Returns:
# for i, group in ldoc.ipairs(retgroups) do
# for ret in group:iter() do
($(ret.type)) $(indent("\t\t\t", ret.text))
# end
# if i < #retgroups then
---or---
# end
# end
# end
# if item.usage then
Usage:
# for i, usage in ldoc.ipairs(item.usage) do
$(sep)$(indent("\t\t\t", usage:gsub("^\n", "")))
# if i < #item.usage then
--------
# end
# end
# end
# if item.see then
See also:
# for see in item.see:iter() do
$(see.mod.name): $(see.name)
# end
# end
*/
# for pos in (module.name.."."):gmatch("()[^.]+%.") do
# local prefix = e(module.name:sub(pos) .. (item.name:match("^[%.%:]") and "" or "."))
{
"trigger": "$(prefix)$(e(item.name))\t$(e(item.summary))",
"contents": "$(prefix)$(e(autocompleteName(item)))"
},
# end
# end
# end
]
}
# end -- if not module
/* Generated by LDoc; sublime-completions template by Reuh. Last updated $(ldoc.updatetime).*/

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -1,2 +1,7 @@
libsf2d/build/
libsf2d/lib/
*.d
*.o
*.project
*.cproject
libsf2d/.settings/*
libsf2d/build/*
libsf2d/lib/*

1
libs/sf2dlib/libsf2d/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/Default/

View file

@ -30,6 +30,7 @@ CFLAGS := -g -Wall -O2\
$(ARCH)
CFLAGS += $(INCLUDE) -DARM11 -D_3DS
#CFLAGS += -std=c11
#WILL HAVE TO BE REMOVED SOON
CFLAGS += -DLIBCTRU_NO_DEPRECATION
@ -138,6 +139,8 @@ $(OUTPUT) : $(OFILES)
@echo "extern const u8" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(notdir $<).shbin | tr . _)`.h
@echo "extern const u32" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(notdir $<).shbin | tr . _)`.h
sf2d.c: shader.vsh
-include $(DEPENDS)
#---------------------------------------------------------------------------------------

View file

@ -4,7 +4,6 @@
* @date 22 March 2015
* @brief sf2dlib header
*/
#ifndef SF2D_H
#define SF2D_H
@ -68,6 +67,14 @@ typedef enum {
TEXFMT_ETC1A4 = 13
} sf2d_texfmt;
/**
* @brief Represents a direction for drawing a gradient
*/
typedef enum {
SF2D_TOP_TO_BOTTOM,
SF2D_LEFT_TO_RIGHT
} sf2d_gradient_dir;
/**
* @brief Data allocated on the RAM or VRAM
@ -136,6 +143,11 @@ typedef struct {
void *data; /**< Pointer to the data */
} sf2d_texture;
typedef struct {
sf2d_texture texture; // "inherit"/extend standard texture
float projection[4*4]; /**< Orthographic projection matrix for this target */
} sf2d_rendertarget;
// Basic functions
/**
@ -171,6 +183,12 @@ void sf2d_set_3D(int enable);
*/
void sf2d_start_frame(gfxScreen_t screen, gfx3dSide_t side);
/**
* @brief Starts a frame bound to a rendertarget
* @param target rendertarget to draw to
*/
void sf2d_start_frame_target(sf2d_rendertarget *target);
/**
* @brief Ends a frame, should be called on pair with sf2d_start_frame
*/
@ -239,9 +257,10 @@ void sf2d_set_clear_color(u32 color);
* @param y0 y coordinate of the first dot
* @param x1 x coordinate of the second dot
* @param y1 y coordinate of the sceond dot
* @param width thickness of the line
* @param color the color to draw the line
*/
void sf2d_draw_line(int x0, int y0, int x1, int y1, u32 color);
void sf2d_draw_line(float x0, float y0, float x1, float y1, float width, u32 color);
/**
* @brief Draws a rectangle
@ -253,6 +272,18 @@ void sf2d_draw_line(int x0, int y0, int x1, int y1, u32 color);
*/
void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color);
/**
* @brief Draws a triangle
* @param x1 x coordinate of a vertex of the triangle
* @param y1 y coordinate of a vertex of the triangle
* @param x2 x coordinate of a vertex of the triangle
* @param y2 y coordinate of a vertex of the triangle
* @param x3 x coordinate of a vertex of the triangle
* @param y3 y coordinate of a vertex of the triangle
* @param color the color to draw the triangle
*/
void sf2d_draw_triangle(float x1, float y1, float x2, float y2, float x3, float y3, u32 color);
/**
* @brief Draws a rotated rectangle
* @param x x coordinate of the top left corner of the rectangle
@ -264,6 +295,31 @@ void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color);
*/
void sf2d_draw_rectangle_rotate(int x, int y, int w, int h, u32 color, float rad);
/**
* @brief Draws a rectangle
* @param x x coordinate of the top left corner of the rectangle
* @param y y coordinate of the top left corner of the rectangle
* @param w rectangle width
* @param h rectangle height
* @param color1 the color at the start of the gradient
* @param color2 the color at the end of the gradient
* @param left_to_right determines which direction the gradient is in
*/
void sf2d_draw_rectangle_gradient(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction);
/**
* @brief Draws a rotated rectangle
* @param x x coordinate of the top left corner of the rectangle
* @param y y coordinate of the top left corner of the rectangle
* @param w rectangle width
* @param h rectangle height
* @param color1 the color at the start of the gradient
* @param color2 the color at the end of the gradient
* @param left_to_right determines which direction the gradient is in
* @param rad rotation (in radians) to draw the rectangle
*/
void sf2d_draw_rectangle_gradient_rotate(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction, float rad);
/**
* @brief Draws a filled circle
* @param x x coordinate of the center of the circle
@ -292,12 +348,37 @@ void sf2d_draw_fill_circle(int x, int y, int radius, u32 color);
*/
sf2d_texture *sf2d_create_texture(int width, int height, sf2d_texfmt pixel_format, sf2d_place place);
/**
* @brief Creates an empty rendertarget.
* Functions similarly to sf2d_create_texture.
* @param width the width of the texture
* @param height the height of the texture
* @return a pointer to the newly created rendertarget
* @note Before drawing the texture, it needs to be tiled
* by calling sf2d_texture_tile32.
* The default texture params are both min and mag filters
* GPU_NEAREST, and both S and T wrappings GPU_CLAMP_TO_BORDER.
*/
sf2d_rendertarget *sf2d_create_rendertarget(int width, int height);
/**
* @brief Frees a texture
* @param texture pointer to the texture to freeze
*/
void sf2d_free_texture(sf2d_texture *texture);
/**
* @brief Frees a rendertarget
* @param target pointer to the rendertarget to free
*/
void sf2d_free_target(sf2d_rendertarget *target);
/**
* @brief Clears a rendertarget to the specified color
* @param target pointer to the rendertarget to clear
*/
void sf2d_clear_target(sf2d_rendertarget *target, u32 color);
/**
* @brief Fills an already allocated texture from a RGBA8 source
* @param dst pointer to the destination texture to fill
@ -419,6 +500,33 @@ void sf2d_draw_texture_rotate(const sf2d_texture *texture, int x, int y, float r
*/
void sf2d_draw_texture_rotate_blend(const sf2d_texture *texture, int x, int y, float rad, u32 color);
/**
* @brief Draws a scaled texture with rotation around its hotspot
* @param texture the texture to draw
* @param x the x coordinate to draw the texture to
* @param y the y coordinate to draw the texture to
* @param rad rotation (in radians) to draw the texture
* @param x_scale the x scale
* @param y_scale the y scale
* @param center_x the x position of the hotspot
* @param center_y the y position of the hotspot
*/
void sf2d_draw_texture_rotate_scale_hotspot(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y);
/**
* @brief Draws a scaled texture with rotation around its hotspot with color
* @param texture the texture to draw
* @param x the x coordinate to draw the texture to
* @param y the y coordinate to draw the texture to
* @param rad rotation (in radians) to draw the texture
* @param x_scale the x scale
* @param y_scale the y scale
* @param center_x the x position of the hotspot
* @param center_y the y position of the hotspot
* @param color the color to blend with the texture
*/
void sf2d_draw_texture_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y, u32 color);
/**
* @brief Draws a part of a texture
* @param texture the texture to draw
@ -525,6 +633,24 @@ void sf2d_draw_texture_part_rotate_scale(const sf2d_texture *texture, int x, int
*/
void sf2d_draw_texture_part_rotate_scale_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, u32 color);
/**
* @brief Draws a part of a texture, with rotation, scaling, color and hotspot
* @param texture the texture to draw
* @param x the x coordinate to draw the texture to
* @param y the y coordinate to draw the texture to
* @param rad rotation (in radians) to draw the texture
* @param tex_x the starting point (x coordinate) where to start drawing
* @param tex_y the starting point (y coordinate) where to start drawing
* @param tex_w the width to draw from the starting point
* @param tex_h the height to draw from the starting point
* @param x_scale the x scale
* @param y_scale the y scale
* @param center_x the x position of the hotspot
* @param center_y the y position of the hotspot
* @param color the color to blend with the texture
*/
void sf2d_draw_texture_part_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, float center_x, float center_y, u32 color);
/**
* @brief Draws a texture blended in a certain depth
* @param texture the texture to draw

View file

@ -7,6 +7,8 @@
void GPU_SetDummyTexEnv(u8 num);
void sf2d_draw_rectangle_internal(const sf2d_vertex_pos_col *vertices);
// Vector operations
void vector_mult_matrix4x4(const float *msrc, const sf2d_vector_3f *vsrc, sf2d_vector_3f *vdst);

View file

@ -1,3 +1,4 @@
#include <string.h>
#include "sf2d.h"
#include "sf2d_private.h"
#include "shader_vsh_shbin.h"
@ -32,6 +33,10 @@ static u32 projection_desc = -1;
//Matrix
static float ortho_matrix_top[4*4];
static float ortho_matrix_bot[4*4];
//Rendertarget things
static sf2d_rendertarget * currentRenderTarget = NULL;
static void * targetDepthBuffer;
static int targetDepthBufferLen = 0;
//Apt hook cookie
static aptHookCookie apt_hook_cookie;
//Functions
@ -111,6 +116,7 @@ int sf2d_fini()
linearFree(gpu_cmd);
vramFree(gpu_fb_addr);
vramFree(gpu_depth_fb_addr);
linearFree(targetDepthBuffer);
sf2d_initialized = 0;
@ -173,6 +179,53 @@ void sf2d_start_frame(gfxScreen_t screen, gfx3dSide_t side)
GPU_SetDummyTexEnv(5);
}
void sf2d_start_frame_target(sf2d_rendertarget *target)
{
sf2d_pool_reset();
GPUCMD_SetBufferOffset(0);
// Upload saved uniform
matrix_gpu_set_uniform(target->projection, projection_desc);
int bufferLen = target->texture.width * target->texture.height * 4; // apparently depth buffer is (or can be) 32bit?
if (bufferLen > targetDepthBufferLen) { // expand depth buffer
if (targetDepthBufferLen > 0) linearFree(targetDepthBuffer);
targetDepthBuffer = linearAlloc(bufferLen);
memset(targetDepthBuffer, 0, bufferLen);
targetDepthBufferLen = bufferLen;
}
GPU_SetViewport((u32 *)osConvertVirtToPhys(targetDepthBuffer),
(u32 *)osConvertVirtToPhys(target->texture.data),
0, 0, target->texture.height, target->texture.width);
currentRenderTarget = target;
GPU_DepthMap(-1.0f, 0.0f);
GPU_SetFaceCulling(GPU_CULL_NONE);
GPU_SetStencilTest(false, GPU_ALWAYS, 0x00, 0xFF, 0x00);
GPU_SetStencilOp(GPU_STENCIL_KEEP, GPU_STENCIL_KEEP, GPU_STENCIL_KEEP);
GPU_SetBlendingColor(0,0,0,0);
GPU_SetDepthTestAndWriteMask(true, GPU_GEQUAL, GPU_WRITE_ALL);
GPUCMD_AddMaskedWrite(GPUREG_EARLYDEPTH_TEST1, 0x1, 0);
GPUCMD_AddWrite(GPUREG_EARLYDEPTH_TEST2, 0);
GPU_SetAlphaBlending(
GPU_BLEND_ADD,
GPU_BLEND_ADD,
GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA,
GPU_ONE, GPU_ZERO
);
GPU_SetAlphaTest(false, GPU_ALWAYS, 0x00);
GPU_SetDummyTexEnv(1);
GPU_SetDummyTexEnv(2);
GPU_SetDummyTexEnv(3);
GPU_SetDummyTexEnv(4);
GPU_SetDummyTexEnv(5);
}
void sf2d_end_frame()
{
GPU_FinishDrawing();
@ -180,23 +233,30 @@ void sf2d_end_frame()
GPUCMD_FlushAndRun();
gspWaitForP3D();
//Copy the GPU rendered FB to the screen FB
if (cur_screen == GFX_TOP) {
GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 400),
(u32 *)gfxGetFramebuffer(GFX_TOP, cur_side, NULL, NULL),
GX_BUFFER_DIM(240, 400), 0x1000);
} else {
GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 320),
(u32 *)gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL),
GX_BUFFER_DIM(240, 320), 0x1000);
}
gspWaitForPPF();
if (!currentRenderTarget) {
//Copy the GPU rendered FB to the screen FB
if (cur_screen == GFX_TOP) {
GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 400),
(u32 *)gfxGetFramebuffer(GFX_TOP, cur_side, NULL, NULL),
GX_BUFFER_DIM(240, 400), 0x1000);
} else {
GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 320),
(u32 *)gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL),
GX_BUFFER_DIM(240, 320), 0x1000);
}
gspWaitForPPF();
//Clear the screen
GX_MemoryFill(
gpu_fb_addr, clear_color, &gpu_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH,
gpu_depth_fb_addr, 0, &gpu_depth_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH);
gspWaitForPSC0();
//Clear the screen
GX_MemoryFill(
gpu_fb_addr, clear_color, &gpu_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH,
gpu_depth_fb_addr, 0, &gpu_depth_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH);
gspWaitForPSC0();
} else {
//gspWaitForPPF();
//gspWaitForPSC0();
sf2d_texture_tile32(&(currentRenderTarget->texture));
}
currentRenderTarget = NULL;
}
void sf2d_swapbuffers()

View file

@ -2,21 +2,11 @@
#include "sf2d_private.h"
#include <math.h>
void sf2d_draw_line(int x0, int y0, int x1, int y1, u32 color)
{
sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8);
if (!vertices) return;
vertices[0].position = (sf2d_vector_3f){(float)x0+1.0f, (float)y0+1.0f, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){(float)x0-1.0f, (float)y0-1.0f, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){(float)x1+1.0f, (float)y1+1.0f, SF2D_DEFAULT_DEPTH};
vertices[3].position = (sf2d_vector_3f){(float)x1-1.0f, (float)y1-1.0f, SF2D_DEFAULT_DEPTH};
vertices[0].color = color;
vertices[1].color = vertices[0].color;
vertices[2].color = vertices[0].color;
vertices[3].color = vertices[0].color;
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
void sf2d_setup_env_internal(const sf2d_vertex_pos_col* vertices) {
GPU_SetTexEnv(
0,
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
@ -38,10 +28,59 @@ void sf2d_draw_line(int x0, int y0, int x1, int y1, u32 color)
(u64[]){0x10}, // attribute permutations for each buffer
(u8[]){2} // number of attributes for each buffer
);
}
void sf2d_draw_line(float x0, float y0, float x1, float y1, float width, u32 color)
{
sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8);
if (!vertices) return;
float dx = x1 - x0;
float dy = y1 - y0;
float nx = -dy;
float ny = dx;
float len = sqrt(nx * nx + ny * ny);
if (len > 0 ){
nx /= len;
ny /= len;
}
nx *= width*0.5f;
ny *= width*0.5f;
vertices[0].position = (sf2d_vector_3f){x0+nx, y0+ny, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){x0-nx, y0-ny, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){x1+nx, y1+ny, SF2D_DEFAULT_DEPTH};
vertices[3].position = (sf2d_vector_3f){x1-nx, y1-ny, SF2D_DEFAULT_DEPTH};
vertices[0].color = color;
vertices[1].color = vertices[0].color;
vertices[2].color = vertices[0].color;
vertices[3].color = vertices[0].color;
sf2d_setup_env_internal(vertices);
GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4);
}
void sf2d_draw_rectangle_internal(const sf2d_vertex_pos_col *vertices)
{
sf2d_setup_env_internal(vertices);
GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4);
}
void sf2d_draw_triangle_internal(const sf2d_vertex_pos_col *vertices)
{
sf2d_setup_env_internal(vertices);
GPU_DrawArray(GPU_TRIANGLES, 0, 3);
}
void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color)
{
sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8);
@ -57,29 +96,23 @@ void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color)
vertices[2].color = vertices[0].color;
vertices[3].color = vertices[0].color;
GPU_SetTexEnv(
0,
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
GPU_TEVOPERANDS(0, 0, 0),
GPU_TEVOPERANDS(0, 0, 0),
GPU_REPLACE, GPU_REPLACE,
0xFFFFFFFF
);
sf2d_draw_rectangle_internal(vertices);
}
GPU_SetAttributeBuffers(
2, // number of attributes
(u32*)osConvertVirtToPhys(vertices),
GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE),
0xFFFC, //0b1100
0x10,
1, //number of buffers
(u32[]){0x0}, // buffer offsets (placeholders)
(u64[]){0x10}, // attribute permutations for each buffer
(u8[]){2} // number of attributes for each buffer
);
void sf2d_draw_triangle(float x1, float y1, float x2, float y2, float x3, float y3, u32 color)
{
sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(3 * sizeof(sf2d_vertex_pos_col), 8);
if (!vertices) return;
GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4);
vertices[0].position = (sf2d_vector_3f){(float)x1, (float)y1, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){(float)x2, (float)y2, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){(float)x3, (float)y3, SF2D_DEFAULT_DEPTH};
vertices[0].color = color;
vertices[1].color = vertices[0].color;
vertices[2].color = vertices[0].color;
sf2d_draw_triangle_internal(vertices);
}
void sf2d_draw_rectangle_rotate(int x, int y, int w, int h, u32 color, float rad)
@ -110,29 +143,56 @@ void sf2d_draw_rectangle_rotate(int x, int y, int w, int h, u32 color, float rad
vertices[i].position = (sf2d_vector_3f){rot[i].x + x + w2, rot[i].y + y + h2, rot[i].z};
}
GPU_SetTexEnv(
0,
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
GPU_TEVOPERANDS(0, 0, 0),
GPU_TEVOPERANDS(0, 0, 0),
GPU_REPLACE, GPU_REPLACE,
0xFFFFFFFF
);
sf2d_draw_rectangle_internal(vertices);
}
GPU_SetAttributeBuffers(
2, // number of attributes
(u32*)osConvertVirtToPhys(vertices),
GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE),
0xFFFC, //0b1100
0x10,
1, //number of buffers
(u32[]){0x0}, // buffer offsets (placeholders)
(u64[]){0x10}, // attribute permutations for each buffer
(u8[]){2} // number of attributes for each buffer
);
void sf2d_draw_rectangle_gradient(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction)
{
sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8);
if (!vertices) return;
GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4);
vertices[0].position = (sf2d_vector_3f){(float)x, (float)y, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){(float)x+w, (float)y, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){(float)x, (float)y+h, SF2D_DEFAULT_DEPTH};
vertices[3].position = (sf2d_vector_3f){(float)x+w, (float)y+h, SF2D_DEFAULT_DEPTH};
vertices[0].color = color1;
vertices[1].color = (direction == SF2D_LEFT_TO_RIGHT) ? color2 : color1;
vertices[2].color = (direction == SF2D_LEFT_TO_RIGHT) ? color1 : color2;
vertices[3].color = color2;
sf2d_draw_rectangle_internal(vertices);
}
void sf2d_draw_rectangle_gradient_rotate(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction, float rad)
{
sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8);
if (!vertices) return;
int w2 = w/2.0f;
int h2 = h/2.0f;
vertices[0].position = (sf2d_vector_3f){(float)-w2, (float)-h2, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){(float) w2, (float)-h2, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){(float)-w2, (float) h2, SF2D_DEFAULT_DEPTH};
vertices[3].position = (sf2d_vector_3f){(float) w2, (float) h2, SF2D_DEFAULT_DEPTH};
vertices[0].color = color1;
vertices[1].color = (direction == SF2D_LEFT_TO_RIGHT) ? color2 : color1;
vertices[2].color = (direction == SF2D_LEFT_TO_RIGHT) ? color1 : color2;
vertices[3].color = color2;
float m[4*4];
matrix_set_z_rotation(m, rad);
sf2d_vector_3f rot[4];
int i;
for (i = 0; i < 4; i++) {
vector_mult_matrix4x4(m, &vertices[i].position, &rot[i]);
vertices[i].position = (sf2d_vector_3f){rot[i].x + x + w2, rot[i].y + y + h2, rot[i].z};
}
sf2d_draw_rectangle_internal(vertices);
}
void sf2d_draw_fill_circle(int x, int y, int radius, u32 color)
@ -165,27 +225,7 @@ void sf2d_draw_fill_circle(int x, int y, int radius, u32 color)
vertices[num_segments + 1].position = vertices[1].position;
vertices[num_segments + 1].color = vertices[1].color;
GPU_SetTexEnv(
0,
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR),
GPU_TEVOPERANDS(0, 0, 0),
GPU_TEVOPERANDS(0, 0, 0),
GPU_REPLACE, GPU_REPLACE,
0xFFFFFFFF
);
GPU_SetAttributeBuffers(
2, // number of attributes
(u32*)osConvertVirtToPhys(vertices),
GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE),
0xFFFC, //0b1100
0x10,
1, //number of buffers
(u32[]){0x0}, // buffer offsets (placeholders)
(u64[]){0x10}, // attribute permutations for each buffer
(u8[]){2} // number of attributes for each buffer
);
sf2d_setup_env_internal(vertices);
GPU_DrawArray(GPU_TRIANGLE_FAN, 0, num_segments + 2);
}

View file

@ -2,6 +2,10 @@
#include <math.h>
#include "sf2d_private.h"
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
//stolen from staplebutt
void GPU_SetDummyTexEnv(u8 num)
{

View file

@ -4,7 +4,11 @@
#include "sf2d.h"
#include "sf2d_private.h"
#define TEX_MIN_SIZE 8
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
#define TEX_MIN_SIZE 32
static unsigned int nibbles_per_pixel(sf2d_texfmt format)
{
@ -93,6 +97,22 @@ sf2d_texture *sf2d_create_texture(int width, int height, sf2d_texfmt pixel_forma
return texture;
}
sf2d_rendertarget *sf2d_create_rendertarget(int width, int height)
{
sf2d_texture *tx = sf2d_create_texture(width, height, TEXFMT_RGBA8, SF2D_PLACE_RAM);
sf2d_rendertarget *rt = malloc(sizeof(*rt));
//memcpy(rt, tx, sizeof(*tx));
rt->texture = *tx;
free(tx);
//tx = * rt->texture;
//rt->projection
matrix_init_orthographic(rt->projection, 0.0f, width, height, 0.0f, 0.0f, 1.0f);
matrix_rotate_z(rt->projection, M_PI / 2.0f);
return rt;
}
void sf2d_free_texture(sf2d_texture *texture)
{
if (texture) {
@ -105,21 +125,61 @@ void sf2d_free_texture(sf2d_texture *texture)
}
}
void sf2d_free_target(sf2d_rendertarget *target)
{
sf2d_free_texture(&(target->texture));
//free(target); // unnecessary since the texture is the start of the target struct
}
void sf2d_clear_target(sf2d_rendertarget *target, u32 color) {
if (color == 0) { // if fully transparent, take a shortcut
memset(target->texture.data, 0, target->texture.width * target->texture.height * 4);
sf2d_texture_tile32(&(target->texture));
return;
}
color = ((color>>24)&0x000000FF) | ((color>>8)&0x0000FF00) | ((color<<8)&0x00FF0000) | ((color<<24)&0xFF000000); // reverse byte order
int itarget = target->texture.width * target->texture.height;
for (int i = 0; i < itarget; i++) { memcpy(target->texture.data + i*4, &color, 4); }
sf2d_texture_tile32(&(target->texture));
}
void sf2d_texture_tile32_hardware(sf2d_texture *texture, const void *data, int w, int h)
{
if (texture->tiled) return;
const u32 flags = (GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) |
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGBA8) |
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO));
GSPGPU_FlushDataCache(data, (w*h)<<2);
GX_DisplayTransfer(
(u32*)data,
GX_BUFFER_DIM(w, h),
(u32*)texture->data,
GX_BUFFER_DIM(texture->pow2_w, texture->pow2_h),
flags
);
gspWaitForPPF();
GSPGPU_InvalidateDataCache(texture->data, texture->data_size);
texture->tiled = 1;
}
void sf2d_fill_texture_from_RGBA8(sf2d_texture *dst, const void *rgba8, int source_w, int source_h)
{
// TODO: add support for non-RGBA8 textures
u8 *tmp = linearAlloc(dst->pow2_w * dst->pow2_h * 4);
u8 *tmp = linearAlloc((dst->pow2_w * dst->pow2_h)<<2);
int i, j;
for (i = 0; i < source_h; i++) {
for (j = 0; j < source_w; j++) {
((u32 *)tmp)[i*dst->pow2_w + j] = ((u32 *)rgba8)[i*source_w + j];
((u32 *)tmp)[i*dst->pow2_w + j] = __builtin_bswap32(((u32 *)rgba8)[i*source_w + j]);
}
}
memcpy(dst->data, tmp, dst->pow2_w*dst->pow2_h*4);
sf2d_texture_tile32_hardware(dst, tmp, dst->pow2_w, dst->pow2_h);
linearFree(tmp);
sf2d_texture_tile32(dst);
}
sf2d_texture *sf2d_create_texture_mem_RGBA8(const void *src_buffer, int src_w, int src_h, sf2d_texfmt pixel_format, sf2d_place place)
@ -344,6 +404,75 @@ void sf2d_draw_texture_rotate_blend(const sf2d_texture *texture, int x, int y, f
color);
}
static inline void sf2d_draw_texture_rotate_scale_hotspot_generic(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y)
{
sf2d_vertex_pos_tex *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_tex), 8);
if (!vertices) return;
const float w = texture->width;
const float h = texture->height;
vertices[0].position.x = -center_x * scale_x;
vertices[0].position.y = -center_y * scale_y;
vertices[0].position.z = SF2D_DEFAULT_DEPTH;
vertices[1].position.x = (w - center_x) * scale_x;
vertices[1].position.y = -center_y * scale_y;
vertices[1].position.z = SF2D_DEFAULT_DEPTH;
vertices[2].position.x = -center_x * scale_x;
vertices[2].position.y = (h - center_y) * scale_y;
vertices[2].position.z = SF2D_DEFAULT_DEPTH;
vertices[3].position.x = (w - center_x) * scale_x;
vertices[3].position.y = h - center_y * scale_y;
vertices[3].position.z = SF2D_DEFAULT_DEPTH;
float u = w/(float)texture->pow2_w;
float v = h/(float)texture->pow2_h;
vertices[0].texcoord = (sf2d_vector_2f){0.0f, 0.0f};
vertices[1].texcoord = (sf2d_vector_2f){u, 0.0f};
vertices[2].texcoord = (sf2d_vector_2f){0.0f, v};
vertices[3].texcoord = (sf2d_vector_2f){u, v};
const float c = cosf(rad);
const float s = sinf(rad);
int i;
for (i = 0; i < 4; ++i) { // Rotate and translate
float _x = vertices[i].position.x;
float _y = vertices[i].position.y;
vertices[i].position.x = _x*c - _y*s + x;
vertices[i].position.y = _x*s + _y*c + y;
}
GPU_SetAttributeBuffers(
2, // number of attributes
(u32*)osConvertVirtToPhys(vertices),
GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 2, GPU_FLOAT),
0xFFFC, //0b1100
0x10,
1, //number of buffers
(u32[]){0x0}, // buffer offsets (placeholders)
(u64[]){0x10}, // attribute permutations for each buffer
(u8[]){2} // number of attributes for each buffer
);
GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4);
}
void sf2d_draw_texture_rotate_scale_hotspot(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y)
{
sf2d_bind_texture(texture, GPU_TEXUNIT0);
sf2d_draw_texture_rotate_scale_hotspot_generic(texture, x, y, rad, scale_x, scale_y, center_x, center_y);
}
void sf2d_draw_texture_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y, u32 color)
{
sf2d_bind_texture_color(texture, GPU_TEXUNIT0, color);
sf2d_draw_texture_rotate_scale_hotspot_generic(texture, x, y, rad, scale_x, scale_y, center_x, center_y);
}
static inline void sf2d_draw_texture_part_generic(const sf2d_texture *texture, int x, int y, int tex_x, int tex_y, int tex_w, int tex_h)
{
sf2d_vertex_pos_tex *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_tex), 8);
@ -489,18 +618,18 @@ void sf2d_draw_texture_part_scale_blend(const sf2d_texture *texture, float x, fl
sf2d_draw_texture_part_scale_generic(texture, x, y, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale);
}
static inline void sf2d_draw_texture_part_rotate_scale_generic(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale)
static inline void sf2d_draw_texture_part_rotate_scale_hotspot_generic(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, float center_x, float center_y)
{
sf2d_vertex_pos_tex *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_tex), 8);
if (!vertices) return;
int w2 = (tex_w * x_scale)/2.0f;
int h2 = (tex_h * y_scale)/2.0f;
int w = tex_w;
int h = tex_h;
vertices[0].position = (sf2d_vector_3f){(float)-w2, (float)-h2, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){(float) w2, (float)-h2, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){(float)-w2, (float) h2, SF2D_DEFAULT_DEPTH};
vertices[3].position = (sf2d_vector_3f){(float) w2, (float) h2, SF2D_DEFAULT_DEPTH};
vertices[0].position = (sf2d_vector_3f){(float)-center_x * x_scale, (float)-center_y * y_scale, SF2D_DEFAULT_DEPTH};
vertices[1].position = (sf2d_vector_3f){(float) (w - center_x) * x_scale, (float)-center_y * y_scale, SF2D_DEFAULT_DEPTH};
vertices[2].position = (sf2d_vector_3f){(float)-center_x * x_scale, (float) (h - center_y) * y_scale, SF2D_DEFAULT_DEPTH};
vertices[3].position = (sf2d_vector_3f){(float) (w - center_x) * x_scale, (float) h - center_y * y_scale, SF2D_DEFAULT_DEPTH};
float u0 = tex_x/(float)texture->pow2_w;
float v0 = tex_y/(float)texture->pow2_h;
@ -540,13 +669,19 @@ static inline void sf2d_draw_texture_part_rotate_scale_generic(const sf2d_textur
void sf2d_draw_texture_part_rotate_scale(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale)
{
sf2d_bind_texture(texture, GPU_TEXUNIT0);
sf2d_draw_texture_part_rotate_scale_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale);
sf2d_draw_texture_part_rotate_scale_hotspot_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, tex_w/2.0f, tex_h/2.0f);
}
void sf2d_draw_texture_part_rotate_scale_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, u32 color)
{
sf2d_bind_texture_color(texture, GPU_TEXUNIT0, color);
sf2d_draw_texture_part_rotate_scale_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale);
sf2d_draw_texture_part_rotate_scale_hotspot_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, tex_w/2.0f, tex_h/2.0f);
}
void sf2d_draw_texture_part_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, float center_x, float center_y, u32 color)
{
sf2d_bind_texture_color(texture, GPU_TEXUNIT0, color);
sf2d_draw_texture_part_rotate_scale_hotspot_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, center_x, center_y);
}
static inline void sf2d_draw_texture_depth_generic(const sf2d_texture *texture, int x, int y, signed short z)

View file

@ -110,9 +110,19 @@ void sftd_draw_wtextf(sftd_font *font, int x, int y, unsigned int color, unsigne
* @param font the font used to calculate the width
* @param size the font size
* @param text a pointer to the text that will be used to calculate the length
* @return the width in pixels
*/
int sftd_get_text_width(sftd_font *font, unsigned int size, const char *text);
/**
* @brief Returns the width of the given wide text in pixels
* @param font the font used to calculate the width
* @param size the font size
* @param text a pointer to the wide text that will be used to calculate the length
* @return the width in pixels
*/
int sftd_get_wtext_width(sftd_font *font, unsigned int size, const wchar_t *text);
/**
* @brief Draws text using a font. The text will wrap after the pixels specified in lineWidth.
* @param font the font to use
@ -149,9 +159,6 @@ void sftd_calc_bounding_box(int *boundingWidth, int *boundingHeight, sftd_font *
*/
void sftd_draw_textf_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text, ...);
// (ctruLua addition) Based on sftd_draw_wtext, returns the width of the text drawn.
int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text);
#ifdef __cplusplus
}
#endif

View file

@ -227,6 +227,13 @@ void sftd_draw_text(sftd_font *font, int x, int y, unsigned int color, unsigned
FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL;
while (*text) {
if(*text == '\n') {
pen_x = x;
pen_y += size;
text++;
continue;
}
glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text);
if (use_kerning && previous && glyph_index) {
@ -304,6 +311,13 @@ void sftd_draw_wtext(sftd_font *font, int x, int y, unsigned int color, unsigned
FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL;
while (*text) {
if(*text == '\n') {
pen_x = x;
pen_y += size;
text++;
continue;
}
glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text);
if (use_kerning && previous && glyph_index) {
@ -417,6 +431,66 @@ int sftd_get_text_width(sftd_font *font, unsigned int size, const char *text)
return pen_x;
}
int sftd_get_wtext_width(sftd_font *font, unsigned int size, const wchar_t *text)
{
FTC_FaceID face_id = (FTC_FaceID)font;
FT_Face face;
FTC_Manager_LookupFace(font->ftcmanager, face_id, &face);
FT_Int charmap_index;
charmap_index = FT_Get_Charmap_Index(face->charmap);
FT_Glyph glyph;
FT_Bool use_kerning = FT_HAS_KERNING(face);
FT_UInt glyph_index, previous = 0;
int pen_x = 0;
int pen_y = size;
FTC_ScalerRec scaler;
scaler.face_id = face_id;
scaler.width = size;
scaler.height = size;
scaler.pixel = 1;
FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL;
while (*text) {
glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text);
if (use_kerning && previous && glyph_index) {
FT_Vector delta;
FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
pen_x += delta.x >> 6;
}
if (!texture_atlas_exists(font->tex_atlas, glyph_index)) {
FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL);
if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) {
continue;
}
}
bp2d_rectangle rect;
int bitmap_left, bitmap_top;
int advance_x, advance_y;
int glyph_size;
texture_atlas_get(font->tex_atlas, glyph_index,
&rect, &bitmap_left, &bitmap_top,
&advance_x, &advance_y, &glyph_size);
const float draw_scale = size/(float)glyph_size;
pen_x += (advance_x >> 16) * draw_scale;
pen_y += (advance_y >> 16) * draw_scale;
previous = glyph_index;
text++;
}
return pen_x;
}
void sftd_draw_text_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text)
{
FTC_FaceID face_id = (FTC_FaceID)font;
@ -610,63 +684,3 @@ void sftd_draw_textf_wrap(sftd_font *font, int x, int y, unsigned int color, uns
sftd_draw_text_wrap(font, x, y, color, size, lineWidth, buffer);
va_end(args);
}
// (ctruLua addition) Based on sftd_draw_wtext, returns the width of the text drawn.
int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text)
{
FTC_FaceID face_id = (FTC_FaceID)font;
FT_Face face;
FTC_Manager_LookupFace(font->ftcmanager, face_id, &face);
FT_Int charmap_index;
charmap_index = FT_Get_Charmap_Index(face->charmap);
FT_Glyph glyph;
FT_Bool use_kerning = FT_HAS_KERNING(face);
FT_UInt glyph_index, previous = 0;
int pen_x = 0;
FTC_ScalerRec scaler;
scaler.face_id = face_id;
scaler.width = size;
scaler.height = size;
scaler.pixel = 1;
FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL;
while (*text) {
glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text);
if (use_kerning && previous && glyph_index) {
FT_Vector delta;
FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
pen_x += delta.x >> 6;
}
if (!texture_atlas_exists(font->tex_atlas, glyph_index)) {
FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL);
if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) {
continue;
}
}
bp2d_rectangle rect;
int bitmap_left, bitmap_top;
int advance_x, advance_y;
int glyph_size;
texture_atlas_get(font->tex_atlas, glyph_index,
&rect, &bitmap_left, &bitmap_top,
&advance_x, &advance_y, &glyph_size);
const float draw_scale = size/(float)glyph_size;
pen_x += (advance_x >> 16) * draw_scale;
previous = glyph_index;
text++;
}
return pen_x;
}

View file

@ -15,6 +15,7 @@ texture_atlas *texture_atlas_create(int width, int height, sf2d_texfmt format, s
rect.h = height;
atlas->tex = sf2d_create_texture(width, height, format, place);
sf2d_texture_set_params(atlas->tex, GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR));
sf2d_texture_tile32(atlas->tex);
atlas->bp_root = bp2d_create(&rect);

6615
libs/stb/include/stb_image.h Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
keyWidth, keyHeight = 25, 25
layout = {
["default"] = {
{ "&", "é", "\"", "'", "(", "-", "è", "_", "ç", "à", ")", "=", "Bks" },
{ "a", "z", "e", "r", "t", "y", "u", "i", "o", "p", "^", "$", "Ent" },
{ "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "ù", "*", "Ent" },
{ "Shift", "<", "w", "x", "c", "v", "b", "n", ",", ";", ":", "!", "Tab" },
{ "SLck", ">", "+", "/", " ", " ", " ", " ", " ", "{", "}", ".", "Sym" }
},
["Shift"] = {
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "°", "+", "Bks" },
{ "A", "Z", "E", "R", "T", "Y", "U", "I", "O", "P", "¨", "£", "Ent" },
{ "Q", "S", "D", "F", "G", "H", "J", "K", "L", "M", "%", "µ", "Ent" },
{ "Shift", ">", "W", "X", "C", "V", "B", "N", "?", ".", "/", "§", "Tab" },
{ "SLck", "~", "#", "[", " ", " ", " ", " ", " ", "]", "|", "@", "Sym" }
},
["Sym"] = {
{ "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}", "Bks" },
{ "a", "z", "€", "r", "t", "y", "u", "i", "o", "p", "", "¤", "Ent" },
{ "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "", "", "Ent" },
{ "Shift", "", "w", "x", "c", "v", "b", "n", "", "", "", "", "Tab" },
{ "SLck", "", "", "", " ", " ", " ", " ", " ", "", "", "", "Sym" }
},
}
alias = {
["Tab"] = "\t",
["Ent"] = "\n",
["Bks"] = "\b"
}
sticky = {
["SLck"] = "Shift"
}
keys = {
["l"] = "Shift",
["r"] = "Shift"
}

View file

@ -15,4 +15,4 @@ return {
["keyword.control"] = hex(0xF92672FF),
["keyword.operator"] = hex(0xF92672FF),
["support.function"] = hex(0x66D9EFFF)
}
}

View file

@ -4,19 +4,23 @@ local gfx = require("ctr.gfx")
-- Open libs
local keyboard = require("keyboard")
local openfile = require("openfile")
local filepicker = require("filepicker")
local color = dofile("color.lua")
local syntax = dofile("syntax.lua")
-- Load data
local font = gfx.font.load("VeraMono.ttf")
local font = gfx.font.load(ctr.root .. "resources/VeraMono.ttf")
-- Open file
local path, status = openfile("Choose a file to edit", nil, nil, "any")
if not path then return end
local path, binding, mode, key = filepicker(nil, {__default = {
a = {filepicker.openFile, "Open"},
y = {filepicker.newFile, "New File"}
}
})
if not mode then return end
local lineEnding
local lines = {}
if status == "exist" then
if mode == "open" then
for line in io.lines(path, "L") do
if not lineEnding then lineEnding = line:match("([\n\r]+)$") end
table.insert(lines, line:match("^(.-)[\n\r]*$"))
@ -31,26 +35,79 @@ local coloredLines = syntax(lines, color)
-- Variables
local lineHeight = 10
local fontSize = 9
local cursorX, cursorY = 1, 1
local scrollX, scrollY = 0, 0
local fileModified = false
-- Helper functions
local function displayedText(text)
return text:gsub("\t", " ")
return text:gsub("\t", " "), nil
end
local function drawTop(eye)
-- Depth multiplicator. Multiply by a positive and add to x to add depth; multiply by a negative and add to x to go out of the screen.
local function d(mul) return math.floor(mul * hid.pos3d() * eye) end
-- Lines
local sI = math.floor(scrollY / lineHeight)
if sI < 1 then sI = 1 end
local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight)
if eI > #lines then eI = #lines end
for i = sI, eI, 1 do
local x = -scrollX
local y = -scrollY+ (i-1)*lineHeight
for _,colored in ipairs(coloredLines[i]) do
local str = displayedText(colored[1])
gfx.text(x + d(#(lines[i]:match("^%s+") or "")*3), y, str, fontSize, colored[2])
x = x + font:width(str)
end
end
-- Cursor
local curline = lines[cursorY]
gfx.rectangle(-scrollX+ font:width(displayedText(curline:sub(1, (utf8.offset(curline, cursorX) or 0)-1))) + d(#(curline:match("^%s+") or "")*3),
-scrollY+ (cursorY-1)*lineHeight, 1, lineHeight, 0, color.cursor)
end
-- Set defaults
gfx.set3D(false)
gfx.set3D(true)
gfx.color.setDefault(color.default)
gfx.color.setBackground(color.background)
gfx.font.setDefault(font)
gfx.font.setSize(fontSize)
while ctr.run() do
hid.read()
local keys = hid.keys()
-- Keys input
if keys.down.start then return end
if keys.down.start then
local exit = not fileModified
if fileModified then
while ctr.run() do
hid.read()
local keys = hid.keys()
if keys.down.b then
exit = false
break
elseif keys.down.a then
exit = true
break
end
gfx.start(gfx.TOP)
gfx.text(3, 3, "The file was modified but not saved!")
gfx.text(3, 3 + lineHeight, "Are you sure you want to exit without saving?")
gfx.text(3, 3 + lineHeight*2, "Press A to exit and discard the modified file")
gfx.text(3, 3 + lineHeight*3, "Press B to return to the editor")
gfx.stop()
gfx.render()
end
end
if exit then break end
end
if keys.down.dRight then
cursorX = cursorX + 1
@ -95,22 +152,23 @@ while ctr.run() do
for i = 1, #lines, 1 do
file:write(lines[i]..lineEnding)
gfx.start(gfx.TOP)
gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF)
gfx.color.setDefault(color.background)
gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%")
gfx.color.setDefault(color.default)
gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF)
gfx.color.setDefault(color.background)
gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%")
gfx.color.setDefault(color.default)
gfx.stop()
gfx.render()
end
file:flush()
file:close()
fileModified = false
end
end
-- Keyboard input
local input = keyboard.read()
if input then
if input == "BACK" then
if input == "\b" then
if cursorX > utf8.len(lines[cursorY])+1 then cursorX = utf8.len(lines[cursorY])+1 end
if cursorX > 1 then
lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX-1)-1)..
@ -120,64 +178,56 @@ while ctr.run() do
cursorX, cursorY = utf8.len(lines[cursorY-1])+1, cursorY - 1
lines[cursorY] = lines[cursorY]..lines[cursorY+1]
table.remove(lines, cursorY+1)
table.remove(coloredLines, cursorY+1)
end
coloredLines[cursorY] = syntax(lines[cursorY], color)
elseif input == "\n" then
local newline = lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1)
local whitespace = lines[cursorY]:match("^%s+")
if whitespace then newline = whitespace .. newline end
lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1)
coloredLines[cursorY] = syntax(lines[cursorY], color)
table.insert(lines, cursorY + 1, newline)
table.insert(coloredLines, cursorY + 1, syntax(newline, color))
cursorX, cursorY = whitespace and #whitespace+1 or 1, cursorY + 1
else
lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1)..input..
lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1)
coloredLines[cursorY] = syntax(lines[cursorY], color)
cursorX = cursorX + 1
end
fileModified = true
end
-- Draw
gfx.start(gfx.TOP)
-- Lines
local sI = math.floor(scrollY / lineHeight)
if sI < 1 then sI = 1 end
local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight)
if eI > #lines then eI = #lines end
for i = sI, eI, 1 do
local x = -scrollX
local y = -scrollY+ (i-1)*lineHeight
for _,colored in ipairs(coloredLines[i]) do
local str = displayedText(colored[1])
gfx.color.setDefault(colored[2])
gfx.text(x, y, str)
gfx.color.setDefault(color.default)
x = x + font:width(str)
end
end
-- Cursor
local curline = lines[cursorY]
gfx.rectangle(-scrollX+ font:width(displayedText(curline:sub(1, (utf8.offset(curline, cursorX) or 0)-1))),
-scrollY+ (cursorY-1)*lineHeight, 1, lineHeight, 0, color.cursor)
local is3D = hid.pos3d() > 0
gfx.start(gfx.TOP, gfx.LEFT)
drawTop(is3D and 0 or -1)
gfx.stop()
if is3D then
gfx.start(gfx.TOP, gfx.RIGHT)
drawTop(1)
gfx.stop()
end
gfx.start(gfx.BOTTOM)
gfx.text(3, 3, "FPS: "..math.ceil(gfx.getFPS()))
gfx.text(3, 3 + lineHeight, "Press select to save.")
gfx.text(3, 3 + lineHeight*2, "Press start to exit.")
keyboard.draw(5, 115)
keyboard.draw(4, 115)
gfx.stop()
gfx.render()
end
font:unload()
font:unload()

View file

@ -50,7 +50,7 @@ local syntax = {
return function(lines, color)
local ret = {}
for _,line in ipairs(lines) do
for _, line in ipairs(type(lines) == "table" and lines or {lines}) do
local colored = { { line, color.default } }
for _, patterns in ipairs(syntax) do
@ -86,5 +86,5 @@ return function(lines, color)
table.insert(ret, colored)
end
return ret
end
return type(lines) == "table" and ret or ret[1]
end

View file

@ -47,6 +47,7 @@ while true do
gfx.text(5, 65, "Speed: "..(speed*100).."% - Left balance: "..(leftBalance*100).."%")
gfx.stop()
audio.update()
gfx.render()
end

View file

@ -36,6 +36,7 @@ local models = {
[cfgu.MODEL_N3DSXL] = "New 3DS XL"
}
cfgu.init()
while ctr.run() do
hid.read()
keys = hid.keys()
@ -45,7 +46,7 @@ while ctr.run() do
gfx.text(2, 2, "CFGU example")
gfx.text(2, 20, "Region: "..regions[cfgu.getRegion()])
gfx.text(2, 30, "Model: "..models[cfgu.getModel()])
gfx.text(2, 40, "Language: "..models[cfgu.getLanguage()])
gfx.text(2, 40, "Language: "..languages[cfgu.getLanguage()])
gfx.text(2, 50, "Username: "..cfgu.getUsername())
local m,d = cfgu.getBirthday()
gfx.text(2, 60, "Birthday: "..d.."/"..m)

View file

@ -8,8 +8,9 @@ local dMul = 1
local angle = 0
local texture1 = gfx.texture.load("sdmc:/3ds/ctruLua/icon.png");
local texture1 = gfx.texture.load(ctr.root.."icon.png");
if not texture1 then error("Giants ducks came from another planet") end
local tWidth, tHeight = texture1:getSize()
gfx.color.setBackground(gfx.color.RGBA8(200, 200, 200))
gfx.set3D(true)
@ -29,7 +30,7 @@ local function drawStuffIn3D(eye)
gfx.color.setDefault(0xFF0000FF)
gfx.rectangle(x + d(10*math.sin(ctr.time()/500)), y, 20, 20, angle)
gfx.line(50 + d(-6), 50, 75 + d(4), 96, gfx.color.RGBA8(52, 10, 65))
gfx.line(50 + d(-6), 50, 75 + d(4), 96, 1, gfx.color.RGBA8(52, 10, 65))
gfx.circle(125 + d(-8), 125, 16)
end
@ -66,7 +67,7 @@ while ctr.run() do
gfx.text(5, 17, "Hello world, from Lua ! éàçù", 20, gfx.color.RGBA8(0, 0, 0))
gfx.text(5, 50, "Time: "..os.date())
texture1:draw(280, 80, angle);
texture1:draw(280, 80, angle, tWidth/2, tHeight/2);
local cx, cy = hid.circle()
gfx.rectangle(40, 90, 60, 60, 0, 0xDDDDDDFF)

View file

@ -25,19 +25,18 @@ while ctr.run() do
dls = dls + 1
end
gfx.startFrame(gfx.TOP)
gfx.start(gfx.TOP)
gfx.text(0, 0, data)
gfx.text(0, 20, "Downloaded "..dls.." times.")
gfx.endFrame()
gfx.stop()
gfx.startFrame(gfx.BOTTOM)
gfx.start(gfx.BOTTOM)
gfx.text(2, 2, "HTTP Contexts example")
gfx.text(2, 20, "The data is downloaded from '"..addr.."'.")
gfx.text(2, 30, "Press [B] to redownload.")
gfx.endFrame()
gfx.stop()
gfx.render()
end
context:close()

View file

@ -25,12 +25,12 @@ while ctr.run() do
local keys = hid.keys()
if keys.down.start then break end
local infos = qtm.getHeadTrackingInfo()
local infos = qtm.getHeadtrackingInfo()
gfx.start(gfx.TOP)
if infos:checkHeadFullyDetected() then
for i=1, 4 do
gfx.point(infos:convertCoordToScreen(1, 400, 240))
gfx.point(infos:convertCoordToScreen(i, 400, 240))
end
end
gfx.stop()

View file

@ -0,0 +1,318 @@
local ctr = require('ctr')
local keyboard = require('keyboard')
local gfx = ctr.gfx
local externalConfig
local function gfxPrepare()
local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(),
gfx.font.getDefault(), gfx.font.getSize()}
local mono = gfx.font.load(ctr.root .. "resources/VeraMono.ttf")
gfx.set3D(false)
gfx.color.setDefault(0xFFFDFDFD)
gfx.color.setBackground(0xFF333333)
gfx.font.setDefault(mono)
gfx.font.setSize(12)
return old
end
local function gfxRestore(state)
gfx.set3D(state[1])
gfx.color.setDefault(state[2])
gfx.color.setBackground(state[3])
gfx.font.setDefault(state[4])
gfx.font.setSize(state[5])
end
local function systemBindings(bindings)
bindings.__default.up = {
function(_, selected, ...)
if selected.inList > 1 then
selected.inList = selected.inList - 1
if selected.inList == selected.offset then
selected.offset = selected.offset - 1
end
end
end
}
bindings.__default.down = {
function(externalConfig, selected, ...)
if selected.inList < #externalConfig.fileList then
selected.inList = selected.inList + 1
if selected.inList - selected.offset >= 16 then
selected.offset = selected.offset + 1
end
end
end
}
bindings.__default.left = {
function(_, selected, ...)
selected.inList, selected.offset = 1, 0
end
}
bindings.__default.right = {
function(externalConfig, selected, ...)
selected.inList = #externalConfig.fileList
if #externalConfig.fileList > 15 then
selected.offset = #externalConfig.fileList - 16
end
end
}
end
local function getFileList(workingDirectory)
local fileList = ctr.fs.list(workingDirectory)
if workingDirectory ~= "/" and workingDirectory ~= "sdmc:/" then
table.insert(fileList, {name = "..", isDirectory = true})
end
-- Stealy stealing code from original openfile.lua
table.sort(fileList, function(i, j)
if i.isDirectory and not j.isDirectory then
return true
elseif i.isDirectory == j.isDirectory then
return string.lower(i.name) < string.lower(j.name)
end
end)
return fileList
end
local function getBinding(selectedFile, bindings)
if selectedFile.isDirectory then
return bindings.__directory, "__directory"
end
for pattern, values in pairs(bindings) do
if selectedFile.name:match(pattern) then
return values, pattern
end
end
return bindings.__default, "__default"
end
local function drawBottom(externalConfig, workingDirectoryScroll, selected)
local workingDirectory = externalConfig.workingDirectory
local bindings = externalConfig.bindings
local selectedFile = externalConfig.fileList[selected.inList]
gfx.start(gfx.BOTTOM)
gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3)
gfx.text(1 - workingDirectoryScroll.value, 0, workingDirectory)
if gfx.font.getDefault():width(workingDirectory) > gfx.BOTTOM_WIDTH - 2 then
workingDirectoryScroll.value = workingDirectoryScroll.value + workingDirectoryScroll.phase
if workingDirectoryScroll.value == (gfx.BOTTOM_WIDTH - 2) - gfx.font.getDefault():width(workingDirectory) or
workingDirectoryScroll.value == 0 then
workingDirectoryScroll.phase = - workingDirectoryScroll.phase
end
end
gfx.text(1, 15, selectedFile.name, 12)
if not selectedFile.isDirectory then
gfx.text(1, 45, tostring(selectedFile.size) .. "B", 12, 0xFF727272)
end
local binding, pattern = getBinding(selectedFile, bindings)
if selectedFile.isDirectory then
gfx.text(1, 30, bindings.__directory.__name, 12, 0xFF727272)
else
gfx.text(1, 30, binding.__name, 12, 0xFF727272)
end
local bindingNames = {
{"start", "Start"}, {"select", "Select"},
{"x", "X"}, {"y", "Y"},
{"b", "B"}, {"a", "A"},
{"r", "R"}, {"l", "L"},
{"zr", "ZR"}, {"zl", "ZL"},
{"cstickDown", "C Down"}, {"cstickUp", "C Up"},
{"cstickRight", "C Right"}, {"cstickLeft", "C Left"}
}
local j = 0
for i, v in ipairs(bindingNames) do
if binding[v[1]] and binding[v[1]][2] then
j = j + 1
gfx.text(1, gfx.BOTTOM_HEIGHT - 15*j, v[2] .. ": " .. binding[v[1]][2])
end
end
externalConfig.callbacks.drawBottom(externalConfig, selected)
gfx.stop()
end
local function drawTop(externalConfig, selected)
gfx.start(gfx.TOP)
gfx.rectangle(0, (selected.inList-selected.offset-1)*15, gfx.TOP_WIDTH, 16, 0, 0xFF0000B9)
local over = #externalConfig.fileList - selected.offset >= 16 and 16 or #externalConfig.fileList - selected.offset
for i=selected.offset+1, selected.offset+over do
local color = externalConfig.fileList[i].isDirectory and 0xFF727272 or 0xFFFDFDFD
gfx.text(1, (i-selected.offset-1)*15+1, externalConfig.fileList[i].name or "", 12, color)
end
externalConfig.callbacks.drawTop(externalConfig, selected)
gfx.stop()
end
local function eventHandler(externalConfig, selected)
externalConfig.callbacks.eventHandler(externalConfig, selected)
ctr.hid.read()
local state = ctr.hid.keys()
local binding, pattern = getBinding(externalConfig.fileList[selected.inList], externalConfig.bindings)
for k, v in pairs(binding) do
if k ~= "__name" and state.down[k] then
local a, b, c, key = v[1](externalConfig, selected, pattern, k)
if key then return a, b, c, key
else return end
end
end
for k, v in pairs(externalConfig.bindings.__default) do
if k ~= "__name" and state.down[k] then
local a, b, c, key = v[1](externalConfig, selected, pattern, k)
if key then return a, b, c, key
else break end
end
end
end
local function nothing(externalConfig, selected, bindingName, bindingKey)
-- externalConfig = {workingDirectory=string, bindings=table, callbacks=table, additionalArguments=table, fileList=table}
-- selected = {file=string, inList=number, offset=number}
-- bindings = {__default/__directory/[regex] = {__name, [keyName] = {(handlingFunction), (name)}}}
-- callbacks = {drawTop, drawBottom, eventHandler}
end
local function changeDirectory(externalConfig, selected, bindingName, bindingKey)
if externalConfig.fileList[selected.inList].isDirectory then
if externalConfig.fileList[selected.inList].name == ".." then
externalConfig.workingDirectory = externalConfig.workingDirectory:gsub("[^/]+/$", "")
else
externalConfig.workingDirectory = externalConfig.workingDirectory .. externalConfig.fileList[selected.inList].name .. "/"
end
externalConfig.fileList = getFileList(externalConfig.workingDirectory)
selected.inList, selected.offset = 1, 0
end
end
local function newFile(externalConfig, selected, bindingName, bindingKey)
local name = ""
while ctr.run() do
gfx.start(gfx.BOTTOM)
gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3)
gfx.text(1, 0, externalConfig.workingDirectory)
keyboard.draw(4, 115)
gfx.stop()
gfx.start(gfx.TOP)
gfx.rectangle(0, 0, gfx.TOP_WIDTH, 16, 0, 0xFF0000B3)
gfx.text(1, 0, "Creating new file")
gfx.rectangle(4, gfx.TOP_HEIGHT // 2 - 15, gfx.TOP_WIDTH - 8, 30, 0, 0xFF727272)
gfx.text(5, gfx.TOP_HEIGHT // 2 - 6, name, 12)
gfx.stop()
gfx.render()
local char = (keyboard.read() or ""):gsub("[\t%/%?%<%>%\\%:%*%|%”%^]", "")
ctr.hid.read()
local keys = ctr.hid.keys()
if char ~= "" and char ~= "\b" and char ~= "\n" then
name = name .. char
elseif char ~= "" and char == "\b" then
name = name:sub(1, (utf8.offset(name, -1) or 0)-1)
elseif (char ~= "" and char == "\n" or keys.down.a) and name ~= "" then
local b, p = getBinding({name=name}, externalConfig.bindings)
return externalConfig.workingDirectory .. name, p, "new", b
elseif keys.down.b then
break
end
end
end
local function openFile(externalConfig, selected, bindingName, bindingKey)
return externalConfig.workingDirectory .. externalConfig.fileList[selected.inList].name,
bindingName, "open", bindingKey
end
local function filePicker(workingDirectory, bindings, callbacks, ...)
-- Argument sanitization
local additionalArguments = { ... }
workingDirectory = workingDirectory or ctr.fs.getDirectory()
bindings = bindings or {}
callbacks = callbacks or {}
for _, v in ipairs({"drawTop", "drawBottom", "eventHandler"}) do
if not callbacks[v] then
callbacks[v] = function(...) end
end
end
if workingDirectory:sub(utf8.offset(workingDirectory, -1) or -1) ~= "/" then
workingDirectory = workingDirectory .. "/"
end
-- Default Bindings
bindings.__default = bindings.__default or {}
bindings.__default.__name = bindings.__default.__name or "File"
bindings.__default.x = bindings.__default.x or {
function(externalConfig, ...)
return externalConfig.workingDirectory, "__directory", nil, "x"
end, "Quit"
}
bindings.__directory = bindings.__directory or {}
bindings.__directory.__name = bindings.__directory.__name or "Directory"
bindings.__directory.a = bindings.__directory.a or {
changeDirectory, "Open"
}
local movementKeys = {
"up" , "down" , "left" , "right" ,
"cpadUp", "cpadDown", "cpadLeft", "cpadRight",
"dUp" , "dDown" , "dLeft" , "dRight"
}
for k, v in pairs(bindings) do
if k ~= "__default" then
setmetatable(bindings[k], {__index = bindings.__default})
end
for _, w in ipairs(movementKeys) do
if v[w] then bindings[k][w] = nil end
end
end
systemBindings(bindings)
-- Other Initialization
local selected = {inList = 1, offset = 0}
local workingDirectoryScroll = { value = 0, phase = -1 }
local gfxState = gfxPrepare()
-- Main Loop
externalConfig = {workingDirectory=workingDirectory, bindings=bindings,
callbacks=callbacks, additionalArguments=additionalArguments,
fileList=getFileList(workingDirectory)}
while ctr.run() do
drawBottom(externalConfig, workingDirectoryScroll, selected)
drawTop(externalConfig, selected)
gfx.render()
local file, binding, mode, key = eventHandler(externalConfig, selected)
if key then
gfxRestore(gfxState)
return file, binding, mode, key
end
end
end
local returnTable = {filePicker = filePicker, openFile = openFile,
newFile = newFile, changeDirectory = changeDirectory}
setmetatable(returnTable, {__call = function(self, ...) return self.filePicker(...) end})
return returnTable

View file

@ -1,44 +1,11 @@
local ctr = require("ctr")
local hid = require("ctr.hid")
local gfx = require("ctr.gfx")
local hex = gfx.color.hex
-- Options
local keyWidth, keyHeight = 25, 25
local layout = {
["default"] = {
{ "&", "é", "\"", "'", "(", "-", "è", "_", "ç", "à", ")", "=", "Back" },
{ "a", "z", "e", "r", "t", "y", "u", "i", "o", "p", "^", "$", "Enter" },
{ "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "ù", "*", "Enter" },
{ "Shift", "<", "w", "x", "c", "v", "b", "n", ",", ";", ":", "!", "Tab" },
{ "CpLck", ">", "+", "/", " ", " ", " ", " ", " ", "{", "}", ".", "AltGr" }
},
["Shift"] = {
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "°", "+", "Back" },
{ "A", "Z", "E", "R", "T", "Y", "U", "I", "O", "P", "¨", "£", "Enter" },
{ "Q", "S", "D", "F", "G", "H", "J", "K", "L", "M", "%", "µ", "Enter" },
{ "Shift", ">", "W", "X", "C", "V", "B", "N", "?", ".", "/", "§", "Tab" },
{ "CpLck", "~", "#", "[", " ", " ", " ", " ", " ", "]", "|", "@", "AltGr" }
},
["AltGr"] = {
{ "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}", "Back" },
{ "a", "z", "", "r", "t", "y", "u", "i", "o", "p", "", "¤", "Enter" },
{ "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "", "", "Enter" },
{ "Shift", "", "w", "x", "c", "v", "b", "n", "", "", "", "", "Tab" },
{ "CpLck", "", "", "", " ", " ", " ", " ", " ", "", "", "", "AltGr" }
},
}
local alias = {
["Tab"] = "\t",
["Enter"] = "\n",
["Back"] = "BACK"
}
local sticky = {
["CpLck"] = "Shift"
}
local keys = {
["l"] = "Shift",
["r"] = "Shift"
}
local config = {}
loadfile(ctr.root .. "config/keyboard.cfg", nil, config)()
-- Variables
local currentModifier = { "default", "sticky" }
@ -51,7 +18,7 @@ return {
local xTouch, yTouch
if hidKeys.down.touch then xTouch, yTouch = hid.touch() end
for key, modifier in pairs(keys) do
for key, modifier in pairs(config.keys) do
if hidKeys.down[key] then
currentModifier = { modifier, "key" }
elseif hidKeys.up[key] and currentModifier[2] == "key" and currentModifier[1] == modifier then
@ -59,27 +26,27 @@ return {
end
end
for row, rowKeys in pairs(layout[currentModifier[1]]) do
for row, rowKeys in pairs(config.layout[currentModifier[1]]) do
for column, key in pairs(rowKeys) do
local xKey, yKey = x + (column-1)*(keyWidth-1), y + (row-1)*(keyHeight-1)
local xKey, yKey = x + (column-1)*(config.keyWidth-1), y + (row-1)*(config.keyHeight-1)
gfx.rectangle(xKey, yKey, keyWidth, keyHeight, 0, hex(0xFFFFFFFF))
gfx.rectangle(xKey + 1, yKey + 1, keyWidth - 2, keyHeight - 2, 0, hex(0x000000FF))
gfx.rectangle(xKey, yKey, config.keyWidth, config.keyHeight, 0, hex(0xFFFFFFFF))
gfx.rectangle(xKey + 1, yKey + 1, config.keyWidth - 2, config.keyHeight - 2, 0, hex(0x000000FF))
gfx.text(xKey + 2, yKey + 2, key)
if xTouch then
if xTouch > xKey and xTouch < xKey + keyWidth then
if yTouch > yKey and yTouch < yKey + keyHeight then
gfx.rectangle(xKey, yKey, keyWidth, keyHeight, 0, hex(0xDDFFFFFF))
if xTouch > xKey and xTouch < xKey + config.keyWidth then
if yTouch > yKey and yTouch < yKey + config.keyHeight then
gfx.rectangle(xKey, yKey, config.keyWidth, config.keyHeight, 0, hex(0xDDFFFFFF))
local k = alias[key] or key
if sticky[k] and layout[sticky[k]] then
if currentModifier[1] == sticky[k] and currentModifier[2] == "sticky" then
local k = config.alias[key] or key
if config.sticky[k] and config.layout[config.sticky[k]] then
if currentModifier[1] == config.sticky[k] and currentModifier[2] == "sticky" then
currentModifier = { "default", "sticky" }
else
currentModifier = { sticky[k], "sticky" }
currentModifier = { config.sticky[k], "sticky" }
end
elseif layout[k] then
elseif config.layout[k] then
if currentModifier[1] == k and currentModifier[2] == "normal" then
currentModifier = { "default", "sticky" }
else

View file

@ -1,156 +0,0 @@
-- Sort ctr.fs.list returns (directories first and alphabetical sorting)
local function sort(files)
table.sort(files, function(i, j)
if i.isDirectory and not j.isDirectory then
return true
elseif i.isDirectory == j.isDirectory then
return string.lower(i.name) < string.lower(j.name)
end
end)
return files
end
--- Open a file explorer to select a file.
-- string title: title of the file explorer.
-- string curdir: the directory to initially open the file explorer in, or nil for the current directory.
-- string exts: the file extensions the user can select, separated by ";". If nil, all extensions are accepted.
-- string type: "exist" to select an existing file, "new" to select an non-existing file or "any" to select a existing
-- or non-existing file name. If nil, defaults to "exist".
-- returns string: the file the user has selected, or nil if the explorer was closed without selecting a file.
-- string: "exist" if the file exist or "new" if it doesn't
return function(title, curdir, exts, type)
-- Open libs
local ctr = require("ctr")
local gfx = require("ctr.gfx")
local keyboard = require("keyboard")
-- Arguments
local curdir = curdir or ctr.fs.getDirectory()
local type = type or "exist"
-- Variables
local sel = 1
local scroll = 0
local files = sort(ctr.fs.list(curdir))
if curdir ~= "/" then table.insert(files, 1, { name = "..", isDirectory = true }) end
local newFileName = ""
local ret = nil
-- Remember and set defaults
local was3D = gfx.get3D()
local wasDefault = gfx.color.getDefault()
local wasBackground = gfx.color.getBackground()
local wasFont = gfx.font.getDefault()
gfx.set3D(false)
gfx.color.setDefault(0xFFFFFFFF)
gfx.color.setBackground(0xFF000000)
gfx.font.setDefault()
while ctr.run() do
ctr.hid.read()
local keys = ctr.hid.keys()
if keys.down.start then break end
-- Keys input
if keys.down.down and sel < #files then
sel = sel + 1
if sel > scroll + 14 then scroll = scroll + 1 end
elseif keys.down.up and sel > 1 then
sel = sel - 1
if sel <= scroll then scroll = scroll - 1 end
end
if keys.down.a then
local f = files[sel]
if f.isDirectory then
if f.name == ".." then curdir = curdir:gsub("[^/]+/$", "")
else curdir = curdir..f.name.."/" end
sel = 1
scroll = 0
files = sort(ctr.fs.list(curdir))
if curdir ~= "/" then
table.insert(files, 1, { name = "..", isDirectory = true })
end
elseif type == "exist" or type == "any" then
if exts then
for ext in (exts..";"):gmatch("[^;]+") do
if f.name:match("%..+$") == ext then
ret = { curdir..f.name, "exist" }
break
end
end
else
ret = { curdir..f.name, "exist" }
end
if ret then break end
end
end
-- Keyboard input
if type == "new" or type == "any" then
local input = keyboard.read()
if input then
if input == "BACK" then
newFileName = newFileName:sub(1, (utf8.offset(newFileName, -1) or 0)-1)
elseif input == "\n" then
local fileStatus = "new"
local f = io.open(curdir..newFileName)
if f then fileStatus = "exist" f:close() end
ret = { curdir..newFileName, fileStatus }
break
else
newFileName = newFileName..input
end
end
end
-- Draw
gfx.start(gfx.TOP)
gfx.rectangle(0, 10+(sel-scroll)*15, gfx.TOP_WIDTH, 15, 0, gfx.color.RGBA8(0, 0, 200))
for i = scroll+1, scroll+14, 1 do
local f = files[i]
if not f then break end
local name = f.isDirectory and "["..f.name.."]" or f.name.." ("..f.fileSize.."b)"
if not f.isHidden then gfx.text(5, 12+(i-scroll)*15, name) end
end
gfx.rectangle(0, 0, gfx.TOP_WIDTH, 25, 0, gfx.color.RGBA8(200, 200, 200))
gfx.text(3, 3, curdir, 13, gfx.color.RGBA8(0, 0, 0))
gfx.stop()
gfx.start(gfx.BOTTOM)
gfx.text(5, 5, title)
gfx.text(5, 20, "Accepted file extensions: "..(exts or "all"))
if type == "new" or type == "any" then
gfx.text(5, 90, newFileName)
keyboard.draw(5, 115)
end
gfx.stop()
gfx.render()
end
-- Reset defaults
gfx.set3D(was3D)
gfx.color.setDefault(wasDefault)
gfx.color.setBackground(wasBackground)
gfx.font.setDefault(wasFont)
if ret then
return table.unpack(ret)
else
return ret
end
end

View file

@ -26,7 +26,7 @@ local function draw(self, x, y, rad)
local tsx, tsy = self.texture:getSize()
local sx, sy = getBox(tsx, tsy, frame, self.frameSizeX, self.frameSizeY)
self.texture:drawPart(x, y, sx, sy, self.frameSizeX, self.frameSizeY, rad)
self.texture:drawPart(x, y, sx, sy, self.frameSizeX, self.frameSizeY, rad, self.offsetX, self.offsetY)
return frame
end
@ -52,20 +52,28 @@ local function resetTimer(self)
self.frameTimer = ctr.time()
end
local function setOffset(self, x, y)
self.offsetX = x or 0
self.offsetY = y or self.offsetX
end
-- Sprite object constructor
function mod.new(texture, fsx, fsy)
return {
texture = texture,
frameSizeX = fsx,
frameSizeY = fsy,
offsetX = 0,
offsetY = 0,
animations = {},
currentAnimation = 0,
currentFrame = 1,
frameTimer = 0,
frameTimer = ctr.time(),
draw = draw,
addAnimation = addAnimation,
setAnimation = setAnimation,
setOffset = setOffset,
resetTimer = resetTimer,
}
end

View file

@ -1,33 +1,55 @@
local ctr = require("ctr")
local fs = require("ctr.fs")
local gfx = require("ctr.gfx")
local fs = require("ctr.fs")
-- Set up path
local ldir = fs.getDirectory().."libs/"
local ldir = ctr.root.."libs/"
package.path = package.path..";".. ldir.."?.lua;".. ldir.."?/init.lua"
local filepicker = require("filepicker")
repeat
-- Erroring
local function displayError(err, trace)
gfx.set3D(false)
gfx.color.setDefault(0xFFFFFFFF)
gfx.color.setBackground(0xFF000000)
gfx.font.setDefault()
local file = require("openfile")("Choose a Lua file to execute", nil, ".lua", "exist")
if file then
fs.setDirectory(file:match("^(.-)[^/]*$"))
local success, err = pcall(dofile, file)
if not success then
local hid = require("ctr.hid")
gfx.set3D(false)
gfx.color.setDefault(0xFFFFFFFF)
gfx.color.setBackground(0xFF000000)
gfx.font.setDefault()
while true do
hid.read()
if hid.keys().down.start then break end
gfx.start(gfx.TOP)
gfx.wrappedText(0, 0, err, gfx.TOP_WIDTH)
gfx.stop()
gfx.render()
end
end
gfx.color.setBackground(0xFF0000B3)
gfx.color.setDefault(0xFFFDFDFD)
gfx.font.setSize(12)
gfx.font.setDefault(gfx.font.load(ctr.root .. "resources/VeraMono.ttf"))
gfx.disableConsole()
while ctr.run() do
gfx.start(gfx.BOTTOM)
gfx.text(1, 1, "An error has occured.")
gfx.wrappedText(1, 30, err, gfx.BOTTOM_WIDTH-2)
gfx.text(1, gfx.BOTTOM_HEIGHT-15, "Press Start to continue.")
gfx.stop()
gfx.start(gfx.TOP)
gfx.wrappedText(2, 6, trace, gfx.TOP_WIDTH - 2)
gfx.stop()
gfx.render()
ctr.hid.read()
if ctr.hid.keys().down.start then break end
end
until not file
end
-- Main loop
while ctr.run() do
gfx.set3D(false)
gfx.font.setDefault()
gfx.color.setDefault(0xFFFDFDFD)
gfx.color.setBackground(0xFF333333)
local file, ext, mode, key = filepicker(ctr.root, {
["%.lua$"] = {
a = {filepicker.openFile, "Run"},
__name = "Lua Script"
}
})
if mode then
fs.setDirectory(file:match("^(.-)[^/]*$"))
xpcall(dofile, function(err) displayError(err, debug.traceback()) end, file)
else
break
end
end
error("Main process has exited.\nPlease reboot.\nPressing Start does not work yet.")

View file

@ -1,37 +0,0 @@
local ctr = require("ctr")
local gfx = require("ctr.gfx")
local hid = require("ctr.hid")
local httpc = require("ctr.httpc")
local err = 0
--assert(httpc.init())
local context = assert(httpc.context())
assert(context:open("http://firew0lf.github.io/"))
assert(context:beginRequest())
local data = assert(context:downloadData())
while ctr.run() do
hid.read()
keys = hid.keys()
if keys.held.start then break end
if keys.down.b then
assert(context:open("http://firew0lf.github.io/"))
assert(context:beginRequest())
data = assert(context:downloadData())
data = (data.."!")
end
gfx.start(gfx.TOP)
gfx.text(0, 0, data)
gfx.stop()
gfx.render()
end
context:close()
--httpc.shutdown()

View file

@ -13,68 +13,29 @@ Used to manage the applets and application status.
#include <lua.h>
#include <lauxlib.h>
/***
Initialize the APT module. Useless.
@function init
*/
static int apt_init(lua_State *L) {
Result ret = aptInit();
if (ret!=0) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
lua_pushboolean(L, true);
return 1;
}
/***
Shutdown the APT module. Useless, don't use it.
@function shutdown
*/
static int apt_shutdown(lua_State *L) {
aptExit();
return 0;
}
/***
Open an APT session. Should only work if you don't use the homebrew menu.
@function openSession
*/
static int apt_openSession(lua_State *L) {
aptOpenSession();
return 0;
}
/***
Close the current APT session.
@function closeSession
*/
static int apt_closeSession(lua_State *L) {
aptCloseSession();
return 0;
}
/***
Set the app status.
@function setStatus
@tparam integer status the new app status
*/
static int apt_setStatus(lua_State *L) {
APT_AppStatus status = luaL_checkinteger(L, 1);
aptSetStatus(status);
return 0;
}
/***
Get the app status.
@function getStatus
@treturn integer the app status
*/
static int apt_getStatus(lua_State *L) {
APT_AppStatus status = aptGetStatus();
lua_pushinteger(L, status);
return 1;
}
@ -84,6 +45,7 @@ Return to the Home menu.
*/
static int apt_returnToMenu(lua_State *L) {
aptReturnToMenu();
return 0;
}
@ -94,8 +56,9 @@ Get the power status.
*/
static int apt_getStatusPower(lua_State *L) {
u32 status = aptGetStatusPower();
lua_pushboolean(L, status);
return 1;
}
@ -106,9 +69,9 @@ Set the power status.
*/
static int apt_setStatusPower(lua_State *L) {
u32 status = lua_toboolean(L, 1);
aptSetStatusPower(status);
return 0;
}
@ -118,6 +81,7 @@ Signal that the application is ready for sleeping.
*/
static int apt_signalReadyForSleep(lua_State *L) {
aptSignalReadyForSleep();
return 0;
}
@ -128,21 +92,60 @@ Return the Home menu AppID.
*/
static int apt_getMenuAppID(lua_State *L) {
lua_pushinteger(L, aptGetMenuAppID());
return 1;
}
/***
Allow or not the system to enter sleep mode.
@function setSleepAllowed
@tparam boolean allowed `true` to allow, `false` to disallow
*/
static int apt_setSleepAllowed(lua_State *L) {
bool allowed = lua_toboolean(L, 1);
aptSetSleepAllowed(allowed);
return 0;
}
/***
Check if sleep mode is allowed.
@function isSleepAllowed
@treturn boolean `true` is allowed, false if not.
*/
static int apt_isSleepAllowed(lua_State *L) {
lua_pushboolean(L, aptIsSleepAllowed());
return 1;
}
/***
Checks whether the system is a New 3DS.
@function isNew3DS
@treturn boolean `true` if it's a New3DS, false otherwise
*/
static int apt_isNew3DS(lua_State *L) {
bool isNew3ds;
APT_CheckNew3DS(&isNew3ds);
lua_pushboolean(L, isNew3ds);
return 1;
}
static const struct luaL_Reg apt_lib[] = {
{"init", apt_init },
{"shutdown", apt_shutdown },
{"openSession", apt_openSession },
{"closeSession", apt_closeSession },
{"setStatus", apt_setStatus },
{"getStatus", apt_getStatus },
{"returnToMenu", apt_returnToMenu },
{"getStatusPower", apt_getStatusPower },
{"setStatusPower", apt_setStatusPower },
{"signalReadyForSleep", apt_signalReadyForSleep},
{"getMenuAppID", apt_getMenuAppID },
{ "setStatus", apt_setStatus },
{ "getStatus", apt_getStatus },
{ "returnToMenu", apt_returnToMenu },
{ "getStatusPower", apt_getStatusPower },
{ "setStatusPower", apt_setStatusPower },
{ "signalReadyForSleep", apt_signalReadyForSleep },
{ "getMenuAppID", apt_getMenuAppID },
{ "setSleepAllowed", apt_setSleepAllowed },
{ "isSleepAllowed", apt_isSleepAllowed },
{ "isNew3DS", apt_isNew3DS },
{NULL, NULL}
};
@ -268,37 +271,45 @@ struct { char *name; int value; } apt_constants[] = {
*/
{"APTSIGNAL_HOMEBUTTON", APTSIGNAL_HOMEBUTTON },
/***
@field APTSIGNAL_PREPARESLEEP
@field APTSIGNAL_HOMEBUTTON2
*/
{"APTSIGNAL_PREPARESLEEP", APTSIGNAL_PREPARESLEEP},
{"APTSIGNAL_HOMEBUTTON2", APTSIGNAL_HOMEBUTTON2 },
/***
@field APTSIGNAL_ENTERSLEEP
@field APTSIGNAL_SLEEP_QUERY
*/
{"APTSIGNAL_ENTERSLEEP", APTSIGNAL_ENTERSLEEP },
{"APTSIGNAL_SLEEP_QUERY", APTSIGNAL_SLEEP_QUERY },
/***
@field APTSIGNAL_SLEEP_CANCEL
*/
{"APTSIGNAL_SLEEP_CANCEL", APTSIGNAL_SLEEP_CANCEL},
/***
@field APTSIGNAL_SLEEP_ENTER
*/
{"APTSIGNAL_SLEEP_ENTER", APTSIGNAL_SLEEP_ENTER },
/***
@field APTSIGNAL_WAKEUP
*/
{"APTSIGNAL_WAKEUP", APTSIGNAL_WAKEUP },
{"APTSIGNAL_SLEEP_WAKEUP", APTSIGNAL_SLEEP_WAKEUP},
/***
@field APTSIGNAL_ENABLE
@field APTSIGNAL_SHUTDOWN
*/
{"APTSIGNAL_ENABLE", APTSIGNAL_ENABLE },
{"APTSIGNAL_SHUTDOWN", APTSIGNAL_SHUTDOWN },
/***
@field APTSIGNAL_POWERBUTTON
*/
{"APTSIGNAL_POWERBUTTON", APTSIGNAL_POWERBUTTON },
/***
@field APTSIGNAL_UTILITY
@field APTSIGNAL_POWERBUTTON2
*/
{"APTSIGNAL_UTILITY", APTSIGNAL_UTILITY },
{"APTSIGNAL_POWERBUTTON2", APTSIGNAL_POWERBUTTON2},
/***
@field APTSIGNAL_SLEEPSYSTEM
@field APTSIGNAL_TRY_SLEEP
*/
{"APTSIGNAL_SLEEPSYSTEM", APTSIGNAL_SLEEPSYSTEM },
{"APTSIGNAL_TRY_SLEEP", APTSIGNAL_TRY_SLEEP },
/***
@field APTSIGNAL_ERROR
@field APTSIGNAL_ORDERTOCLOSE
*/
{"APTSIGNAL_ERROR", APTSIGNAL_ERROR },
{"APTSIGNAL_ORDERTOCLOSE", APTSIGNAL_ORDERTOCLOSE},
/***
@field APTHOOK_ONSUSPEND
*/
@ -327,6 +338,8 @@ struct { char *name; int value; } apt_constants[] = {
};
int luaopen_apt_lib(lua_State *L) {
aptInit();
luaL_newlib(L, apt_lib);
for (int i = 0; apt_constants[i].name; i++) {
@ -340,3 +353,7 @@ int luaopen_apt_lib(lua_State *L) {
void load_apt_lib(lua_State *L) {
luaL_requiref(L, "ctr.apt", luaopen_apt_lib, false);
}
void unload_apt_lib(lua_State *L) {
aptExit();
}

View file

@ -11,6 +11,7 @@ There are 24 audio channels available, numbered from 0 to 23.
#include <malloc.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <lua.h>
#include <lauxlib.h>
@ -22,30 +23,80 @@ There are 24 audio channels available, numbered from 0 to 23.
typedef enum {
TYPE_UNKNOWN = -1,
TYPE_OGG = 0,
TYPE_WAV = 1
TYPE_WAV = 1,
TYPE_RAW = 2
} filetype;
// Audio object userdata
typedef struct {
filetype type; // file type
// OGG Vorbis specific
OggVorbis_File vf; // ogg vorbis file
// File type specific
union {
// OGG Vorbis
struct {
OggVorbis_File vf;
int currentSection; // section and position at the end of the initial data
long rawPosition;
};
// WAV
struct {
FILE* file;
long fileSize;
long filePosition; // position at the end of the initial data
};
};
// Needed for playback
float rate; // sample rate (per channel) (Hz)
u32 channels; // channel count
u32 encoding; // data encoding (NDSP_ENCODING_*)
// Initial data
u32 nsamples; // numbers of samples in the audio (per channel, not the total)
u32 size; // number of bytes in the audio (total, ie data size)
char* data; // raw audio data
// Other useful data
u16 bytePerSample; // bytes per sample (warning: undefined for ADPCM (only for raw data))
u32 chunkSize; // size per chunk (for streaming)
u32 chunkNsamples; // number of samples per chunk
// Playing parameters (type-independant)
float mix[12]; // mix parameters
ndspInterpType interp; // interpolation type
double speed; // playing speed
} audio_userdata;
// Audio stream instance struct (when an audio is played; only used when streaming)
typedef struct {
audio_userdata* audio;
bool loop; // loop audio?
// Current position information
union {
// OGG
struct {
int currentSection;
long rawPosition;
};
// WAV
long filePosition;
};
double prevStartTime; // audio time when last chunk started playing
bool eof; // if reached end of file
bool done; // if streaming ended and the stream will be skipped on the next update
// (the struct should be keept in memory until replaced or audio stopped or it will break audio:time())
char* nextData; // the next data to play
ndspWaveBuf* nextWaveBuf;
char* prevData; // the data actually playing
ndspWaveBuf* prevWaveBuf;
} audio_stream;
// Indicate if NDSP was initialized or not.
// NDSP doesn't work on citra yet.
// Please only throw an error related to this when using a ndsp function, so other parts of the
@ -55,12 +106,52 @@ bool isAudioInitialized = false;
// Array of the last audio_userdata sent to each channel; channels range from 0 to 23
audio_userdata* channels[24];
// Array of the audio_instance that needs to be updated when calling audio.update (indexed per channel).
audio_stream* streaming[24];
// Stop playing audio on a channel, and stop streaming/free memory.
void stopAudio(int channel) {
ndspChnWaveBufClear(channel);
// Stop streaming and free data
if (streaming[channel] != NULL) {
audio_stream* stream = streaming[channel];
if (stream->nextWaveBuf != NULL) {
free(stream->nextWaveBuf);
stream->nextWaveBuf = NULL;
}
if (stream->nextData != NULL) {
linearFree(stream->nextData);
stream->nextData = NULL;
}
if (stream->prevWaveBuf != NULL) {
free(stream->prevWaveBuf);
stream->prevWaveBuf = NULL;
}
if (stream->prevData != NULL) {
linearFree(stream->prevData);
stream->prevData = NULL;
}
free(stream);
streaming[channel] = NULL;
}
}
/***
Load an audio file.
OGG Vorbis and PCM WAV file format are currently supported.
(Most WAV files use the PCM encoding).
NOTE: audio streaming doesn't use threading for now, this means that the decoding will be done on the main thread.
It should work fine with WAVs, but with OGG files you may suffer slowdowns when a new chunk of data is decoded.
To avoid that, you can either reduce the chunkDuration or disable streaming, but be careful if you do so, audio files
can fill the memory really quickly.
@function load
@tparam string path path to the file
@tparam string path path to the file or the data if type is raw
@tparam[opt=0.1] number chunkDuration if set to -1, streaming will be disabled (all data is loaded in memory at once)
Other values are the stream chunk duration in seconds (ctrµLua will load
the audio per chunk of x seconds). Note that you need to call audio.update() each
frame in order for ctµLua to load new data from audio streams. Two chunks of data
will be loaded at the same time at most (one playing, the other ready to be played).
@tparam[opt=detect] string type file type, `"ogg"` or `"wav"`.
If set to `"detect"`, will try to deduce the type from the filename.
@treturn[1] audio the loaded audio object
@ -69,7 +160,8 @@ OGG Vorbis and PCM WAV file format are currently supported.
*/
static int audio_load(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
const char* argType = luaL_optstring(L, 2, "detect");
double streamChunk = luaL_optnumber(L, 2, 0.1);
const char* argType = luaL_optstring(L, 3, "detect");
// Create userdata
audio_userdata *audio = lua_newuserdata(L, sizeof(*audio));
@ -91,6 +183,8 @@ static int audio_load(lua_State *L) {
type = TYPE_OGG;
} else if (strcmp(argType, "wav") == 0) {
type = TYPE_WAV;
} else if (strcmp(argType, "raw") == 0) {
type = TYPE_RAW;
}
// Open and read file
@ -114,16 +208,26 @@ static int audio_load(lua_State *L) {
audio->encoding = NDSP_ENCODING_PCM16;
audio->nsamples = ov_pcm_total(&audio->vf, -1);
audio->size = audio->nsamples * audio->channels * 2; // *2 because output is PCM16 (2 bytes/sample)
audio->bytePerSample = 2;
if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available");
audio->data = linearAlloc(audio->size);
// Streaming
if (streamChunk < 0) {
audio->chunkNsamples = audio->nsamples;
audio->chunkSize = audio->size;
} else {
audio->chunkNsamples = fmin(round(streamChunk * audio->rate), audio->nsamples);
audio->chunkSize = audio->chunkNsamples * audio->channels * 2;
}
// Allocate
if (linearSpaceFree() < audio->chunkSize) luaL_error(L, "not enough linear memory available");
audio->data = linearAlloc(audio->chunkSize);
// Decoding loop
int offset = 0;
int eof = 0;
int current_section;
while (!eof) {
long ret = ov_read(&audio->vf, &audio->data[offset], 4096, &current_section);
while (!eof && offset < audio->chunkSize) {
long ret = ov_read(&audio->vf, &audio->data[offset], fmin(audio->chunkSize - offset, 4096), &audio->currentSection);
if (ret == 0) {
eof = 1;
@ -137,6 +241,7 @@ static int audio_load(lua_State *L) {
offset += ret;
}
}
audio->rawPosition = ov_raw_tell(&audio->vf);
return 1;
@ -222,13 +327,29 @@ static int audio_load(lua_State *L) {
return 0;
}
audio->bytePerSample = byte_per_sample / audio->channels;
// Streaming
if (streamChunk < 0) {
audio->chunkNsamples = audio->nsamples;
audio->chunkSize = audio->size;
} else {
audio->chunkNsamples = fmin(round(streamChunk * audio->rate), audio->nsamples);
audio->chunkSize = audio->chunkNsamples * audio->channels * audio->bytePerSample;
}
// Read data
if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available");
audio->data = linearAlloc(audio->size);
if (linearSpaceFree() < audio->chunkSize) luaL_error(L, "not enough linear memory available");
audio->data = linearAlloc(audio->chunkSize);
fread(audio->data, audio->size, 1, file);
fread(audio->data, audio->chunkSize, 1, file);
audio->file = file;
audio->filePosition = ftell(file);
fseek(file, 0, SEEK_END);
audio->fileSize = ftell(file);
fclose(file);
return 1;
} else {
@ -244,6 +365,7 @@ static int audio_load(lua_State *L) {
/***
Load raw audio data from a string.
No streaming.
@function loadRaw
@tparam string data raw audio data
@tparam number rate sampling rate
@ -264,16 +386,18 @@ static int audio_loadRaw(lua_State *L) {
luaL_getmetatable(L, "LAudio");
lua_setmetatable(L, -2);
audio->type = TYPE_WAV;
audio->type = TYPE_RAW;
audio->rate = rate;
audio->channels = channels;
u8 sampleSize = 2; // default to 2
if (strcmp(argEncoding, "PCM8")) {
audio->encoding = NDSP_ENCODING_PCM8;
audio->bytePerSample = 1;
sampleSize = 1;
} else if (strcmp(argEncoding, "PCM16")) {
audio->encoding = NDSP_ENCODING_PCM16;
audio->bytePerSample = 2;
} else if (strcmp(argEncoding, "ADPCM")) {
audio->encoding = NDSP_ENCODING_ADPCM;
} else {
@ -285,6 +409,9 @@ static int audio_loadRaw(lua_State *L) {
audio->nsamples = dataSize/sampleSize;
audio->size = dataSize;
audio->data = data;
audio->chunkSize = audio->size;
audio->chunkNsamples = audio->nsamples;
audio->speed = 1.0;
@ -445,7 +572,7 @@ static int audio_stop(lua_State *L) {
if (channel == -1) {
for (int i = 0; i <= 23; i++) {
if (ndspChnIsPlaying(i)) {
ndspChnWaveBufClear(i);
stopAudio(i);
n++;
}
}
@ -453,7 +580,7 @@ static int audio_stop(lua_State *L) {
luaL_error(L, "channel number must be between 0 and 23");
} else {
if (ndspChnIsPlaying(channel)) {
ndspChnWaveBufClear(channel);
stopAudio(channel);
n++;
}
}
@ -463,6 +590,128 @@ static int audio_stop(lua_State *L) {
return 1;
}
/***
Update all the currently playing audio streams.
Must be called every frame if you want to use audio with streaming.
@function update
*/
static int audio_update(lua_State *L) {
if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");
for (int i = 0; i <= 23; i++) {
if (streaming[i] == NULL) continue;
audio_stream* stream = streaming[i];
if (stream->done) continue;
audio_userdata* audio = stream->audio;
// If the next chunk started to play, load the next one
if (stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) == stream->nextWaveBuf->sequence_id) {
if (stream->prevWaveBuf) stream->prevStartTime = stream->prevStartTime + (double)(audio->chunkNsamples) / audio->rate;
if (!stream->eof) {
// Swap buffers
char* prevData = stream->prevData; // doesn't contain important data, can rewrite
char* nextData = stream->nextData; // contains the data that started playing
stream->prevData = nextData; // buffer in use
stream->nextData = prevData; // now contains an available buffer
stream->prevWaveBuf = stream->nextWaveBuf;
// Decoding loop
u32 chunkNsamples = audio->chunkNsamples; // chunk nsamples and size may be lower than the defaults if reached EOF
u32 chunkSize = audio->chunkSize;
if (audio->type == TYPE_OGG) {
if (ov_seekable(&audio->vf) && ov_raw_tell(&audio->vf) != stream->rawPosition)
ov_raw_seek(&audio->vf, stream->rawPosition); // goto last read end (audio file may be played multiple times at one)
int offset = 0;
while (!stream->eof && offset < audio->chunkSize) {
long ret = ov_read(&audio->vf, &stream->nextData[offset], fmin(audio->chunkSize - offset, 4096), &stream->currentSection);
if (ret == 0) {
stream->eof = 1;
} else if (ret < 0) {
luaL_error(L, "error in the ogg vorbis stream");
return 0;
} else {
offset += ret;
}
}
stream->rawPosition = ov_raw_tell(&audio->vf);
chunkSize = offset;
chunkNsamples = chunkSize / audio->channels / audio->bytePerSample;
} else if (audio->type == TYPE_WAV) {
chunkSize = fmin(audio->fileSize - stream->filePosition, audio->chunkSize);
chunkNsamples = chunkSize / audio->channels / audio->bytePerSample;
fseek(audio->file, stream->filePosition, SEEK_SET); // goto last read end (audio file may be played multiple times at one)
fread(stream->nextData, chunkSize, 1, audio->file);
stream->filePosition = ftell(audio->file);
if (stream->filePosition == audio->fileSize) stream->eof = 1;
} else luaL_error(L, "unknown audio type");
// Send & play audio data
ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));
waveBuf->data_vaddr = stream->nextData;
waveBuf->nsamples = chunkNsamples;
waveBuf->looping = false;
DSP_FlushDataCache((u32*)stream->nextData, chunkSize);
ndspChnWaveBufAdd(i, waveBuf);
stream->nextWaveBuf = waveBuf;
}
}
// Free the last chunk if it's no longer played
if (stream->prevWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->prevWaveBuf->sequence_id) {
free(stream->prevWaveBuf);
stream->prevWaveBuf = NULL;
}
// We're done
if (stream->prevWaveBuf == NULL && stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->nextWaveBuf->sequence_id && stream->eof) {
free(stream->nextWaveBuf);
stream->nextWaveBuf = NULL;
// Free memory
if (!stream->loop) {
linearFree(stream->prevData);
stream->prevData = NULL;
linearFree(stream->nextData);
stream->nextData = NULL;
stream->done = true;
// Loop: goto start
} else {
// Send & play audio initial data
ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));
waveBuf->data_vaddr = audio->data;
waveBuf->nsamples = audio->chunkNsamples;
waveBuf->looping = false;
DSP_FlushDataCache((u32*)audio->data, audio->chunkSize);
ndspChnWaveBufAdd(i, waveBuf);
stream->nextWaveBuf = waveBuf;
// Reset stream values
stream->prevStartTime = 0;
stream->eof = false;
if (audio->type == TYPE_OGG) {
stream->currentSection = audio->currentSection;
stream->rawPosition = audio->rawPosition;
} else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition;
}
}
}
return 0;
}
/***
audio object
@section Methods
@ -505,8 +754,11 @@ static int audio_object_time(lua_State *L) {
if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing
lua_pushnumber(L, 0);
else
lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate);
else {
double additionnalTime = 0;
if (streaming[channel] != NULL) additionnalTime = streaming[channel]->prevStartTime;
lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate + additionnalTime);
}
return 1;
}
@ -642,7 +894,7 @@ static int audio_object_play(lua_State *L) {
if (channel < 0 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");
// Set channel parameters
ndspChnWaveBufClear(channel);
stopAudio(channel);
ndspChnReset(channel);
ndspChnInitParams(channel);
ndspChnSetMix(channel, audio->mix);
@ -650,20 +902,47 @@ static int audio_object_play(lua_State *L) {
ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works
ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding));
// Send & play audio data
// Send & play audio initial data
ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));
waveBuf->data_vaddr = audio->data;
waveBuf->nsamples = audio->nsamples;
waveBuf->looping = loop;
waveBuf->nsamples = audio->chunkNsamples;
waveBuf->looping = (audio->chunkSize < audio->size) ? false : loop; // let ndsp loop the chunk if not streaming
DSP_FlushDataCache((u32*)audio->data, audio->size);
DSP_FlushDataCache((u32*)audio->data, audio->chunkSize);
ndspChnWaveBufAdd(channel, waveBuf);
channels[channel] = audio;
lua_pushinteger(L, channel);
// Remove last audio stream
if (streaming[channel] != NULL) {
free(streaming[channel]);
streaming[channel] = NULL;
}
// Stream the rest of the audio
if (audio->chunkSize < audio->size) {
audio_stream* stream = calloc(1, sizeof(audio_stream));
stream->audio = audio;
stream->loop = loop;
stream->nextWaveBuf = waveBuf;
// Allocate buffers
if (linearSpaceFree() < audio->chunkSize*2) luaL_error(L, "not enough linear memory available");
stream->nextData = linearAlloc(audio->chunkSize);
stream->prevData = linearAlloc(audio->chunkSize);
// Init stream values
if (audio->type == TYPE_OGG) {
stream->currentSection = audio->currentSection;
stream->rawPosition = audio->rawPosition;
} else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition;
streaming[channel] = stream;
}
return 1;
}
@ -688,7 +967,7 @@ static int audio_object_stop(lua_State *L) {
if (channel == -1) {
for (int i = 0; i <= 23; i++) {
if (channels[i] == audio && ndspChnIsPlaying(i)) {
ndspChnWaveBufClear(i);
stopAudio(i);
n++;
}
}
@ -696,7 +975,7 @@ static int audio_object_stop(lua_State *L) {
luaL_error(L, "channel number must be between 0 and 23");
} else {
if (channels[channel] == audio && ndspChnIsPlaying(channel)) {
ndspChnWaveBufClear(channel);
stopAudio(channel);
n++;
}
}
@ -709,7 +988,7 @@ static int audio_object_stop(lua_State *L) {
/***
Returns the audio object type.
@function :type
@treturn string "ogg" or "wav"
@treturn string "ogg", "wav" or "raw"
*/
static int audio_object_type(lua_State *L) {
audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
@ -718,6 +997,10 @@ static int audio_object_type(lua_State *L) {
lua_pushstring(L, "ogg");
else if (audio->type == TYPE_WAV)
lua_pushstring(L, "wav");
else if (audio->type == TYPE_RAW)
lua_pushstring(L, "raw");
else
lua_pushstring(L, "unknown");
return 1;
}
@ -733,12 +1016,13 @@ static int audio_object_unload(lua_State *L) {
if (isAudioInitialized) {
for (int i = 0; i <= 23; i++) {
if (channels[i] == audio) {
ndspChnWaveBufClear(i);
stopAudio(i);
}
}
}
if (audio->type == TYPE_OGG) ov_clear(&audio->vf);
else if (audio->type == TYPE_WAV) fclose(audio->file);
// Free memory
linearFree(audio->data);
@ -872,6 +1156,7 @@ static const struct luaL_Reg audio_lib[] = {
{ "interpolation", audio_interpolation },
{ "speed", audio_speed },
{ "stop", audio_stop },
{ "update", audio_update },
{ NULL, NULL }
};
@ -887,9 +1172,7 @@ int luaopen_audio_lib(lua_State *L) {
}
void load_audio_lib(lua_State *L) {
if (!isAudioInitialized) {
isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
}
if (!isAudioInitialized) isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false);
}

View file

@ -21,6 +21,9 @@ The `cam` module.
/***
Initialize the camera module.
@function init
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int cam_init(lua_State *L) {
Result ret = camInit();

View file

@ -14,13 +14,17 @@ Used to get some user config.
#include <lua.h>
#include <lauxlib.h>
bool initStateCFGU = false;
/***
Initialize the CFGU module.
@function init
*/
static int cfgu_init(lua_State *L) {
cfguInit();
if (!initStateCFGU) {
cfguInit();
initStateCFGU = true;
}
return 0;
}
@ -29,8 +33,10 @@ Disable the CFGU module.
@function shutdown
*/
static int cfgu_shutdown(lua_State *L) {
cfguExit();
if (initStateCFGU) {
cfguExit();
initStateCFGU = false;
}
return 0;
}
@ -103,9 +109,13 @@ static int cfgu_getUsername(lua_State *L) {
CFGU_GetConfigInfoBlk2(0x1C, 0xA0000, (u8*)block);
u8 *name = malloc(0x14);
utf16_to_utf8(name, block, 0x14);
ssize_t len = utf16_to_utf8(name, block, 0x14);
if (len < 0) {
lua_pushstring(L, "");
return 1;
}
lua_pushlstring(L, (const char *)name, 0x14); // The username is only 0x14 characters long.
lua_pushlstring(L, (const char *)name, len); // The username is only 0x14 characters long.
return 1;
}
@ -309,3 +319,10 @@ int luaopen_cfgu_lib(lua_State *L) {
void load_cfgu_lib(lua_State *L) {
luaL_requiref(L, "ctr.cfgu", luaopen_cfgu_lib, false);
}
void unload_cfgu_lib(lua_State *L) {
if (initStateCFGU) {
initStateCFGU = false;
cfguExit();
}
}

View file

@ -3,6 +3,9 @@ The `ctr` module.
@module ctr
@usage local ctr = require("ctr")
*/
#include <stdlib.h>
#include <unistd.h>
#include <3ds/types.h>
#include <3ds/services/apt.h>
#include <3ds/os.h>
@ -25,6 +28,7 @@ The `ctr.news` module.
@see ctr.news
*/
void load_news_lib(lua_State *L);
void unload_news_lib(lua_State *L);
/***
The `ctr.ptm` module.
@ -32,6 +36,7 @@ The `ctr.ptm` module.
@see ctr.ptm
*/
void load_ptm_lib(lua_State *L);
void unload_ptm_lib(lua_State *L);
/***
The `ctr.hid` module.
@ -77,6 +82,7 @@ The `ctr.cfgu` module.
@see ctr.cfgu
*/
void load_cfgu_lib(lua_State *L);
void unload_cfgu_lib(lua_State *L);
/***
The `ctr.socket` module.
@ -106,6 +112,7 @@ The `ctr.apt` module.
@see ctr.apt
*/
void load_apt_lib(lua_State *L);
void unload_apt_lib(lua_State *L);
/***
The `ctr.mic` module.
@ -121,6 +128,14 @@ The `ctr.thread` module.
*/
void load_thread_lib(lua_State *L);
/***
The `ctr.uds` module.
@table uds
@see ctr.uds
*/
void load_uds_lib(lua_State *L);
void unload_uds_lib(lua_State *L);
/***
Return whether or not the program should continue.
@function run
@ -133,11 +148,23 @@ static int ctr_run(lua_State *L) {
}
/***
Return the number of milliseconds since 1st Jan 1900 00:00.
Return the number of milliseconds spent since some point in time.
This can be used to measure a duration with milliseconds precision; however this can't be used to get the current time or date.
See Lua's os.date() for this use.
For various reasons (see the C source), this will actually returns a negative value.
@function time
@treturn number milliseconds
@usage
-- Measuring a duration:
local startTime = ctr.time()
-- do stuff
local duration = ctr.time() - startTime
*/
static int ctr_time(lua_State *L) {
// osGetTime actually returns the number of seconds elapsed since 1st Jan 1900 00:00.
// However, it returns a u64, we build Lua with 32bits numbers, and every number is signed in Lua, so this obvioulsy doesn't work
// and actually returns a negative value. It still works for durations however. Because having the date and time with millisecond-presion
// doesn't really seem useful and changing ctrµLua's API to work on 64bits numbers will take a long time, we choosed to keep this as-is.
lua_pushinteger(L, osGetTime());
return 1;
@ -165,21 +192,22 @@ static const struct luaL_Reg ctr_lib[] = {
// Subtables
struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); } ctr_libs[] = {
{ "gfx", load_gfx_lib, unload_gfx_lib },
{ "news", load_news_lib, NULL },
{ "ptm", load_ptm_lib, NULL },
{ "news", load_news_lib, unload_news_lib },
{ "ptm", load_ptm_lib, unload_ptm_lib },
{ "hid", load_hid_lib, unload_hid_lib },
{ "ir", load_ir_lib, NULL },
{ "fs", load_fs_lib, unload_fs_lib },
{ "httpc", load_httpc_lib, unload_httpc_lib },
{ "qtm", load_qtm_lib, NULL },
{ "cfgu", load_cfgu_lib, NULL },
{ "cfgu", load_cfgu_lib, unload_cfgu_lib },
{ "socket", load_socket_lib, NULL },
{ "cam", load_cam_lib, NULL },
{ "audio", load_audio_lib, unload_audio_lib },
{ "apt", load_apt_lib, NULL },
{ "apt", load_apt_lib, unload_apt_lib },
{ "mic", load_mic_lib, NULL },
{ "thread", load_thread_lib, NULL },
{ NULL, NULL }
{ "uds", load_uds_lib, unload_uds_lib },
{ NULL, NULL, NULL }
};
int luaopen_ctr_lib(lua_State *L) {
@ -189,6 +217,33 @@ int luaopen_ctr_lib(lua_State *L) {
ctr_libs[i].load(L);
lua_setfield(L, -2, ctr_libs[i].name);
}
/***
Running version of ctrµLua. This string contains the exact name of the last (pre-)release tag.
@field version
*/
lua_pushstring(L, CTR_VERSION);
lua_setfield(L, -2, "version");
/***
Running build of ctrµLua. This string contains the last commit hash.
@field build
*/
lua_pushstring(L, CTR_BUILD);
lua_setfield(L, -2, "build");
/***
Root directory of ctrµLua. Contains the working directory where ctrµLua has been launched OR the romfs root if romfs has been enabled.
@field root
*/
#ifdef ROMFS
char* buff = "romfs:/";
chdir(buff);
#else
char* buff = malloc(1024);
getcwd(buff, 1024);
#endif
lua_pushstring(L, buff);
lua_setfield(L, -2, "root");
return 1;
}

View file

@ -1,5 +1,5 @@
/***
The `font` module
The `gfx.font` module
@module ctr.gfx.font
@usage local font = require("ctr.gfx.font")
*/
@ -15,11 +15,16 @@ The `font` module
#include "font.h"
u32 textSize = 9;
/***
Load a TTF font.
Load a font. Supported formats: TTF, OTF, TTC, OTC, WOFF, PFA, PFB, PCF, FNT, BDF, PFR, and others.
ctrµLua support all formats supported by FreeType. See here for a more complete list: http://freetype.org/freetype2/docs/index.html
@function load
@tparam string path path to the file
@treturn font the loaded font.
@treturn[1] font the loaded font.
@treturn[2] nil if an error occurred
@treturn[2] string error message
*/
static int font_load(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
@ -70,6 +75,28 @@ static int font_getDefault(lua_State *L) {
return 1;
}
/***
Set the default text size.
@function setSize
@tparam number size new default text size
*/
static int font_setSize(lua_State *L) {
textSize = luaL_checkinteger(L, 1);
return 0;
}
/***
Return the default text size.
@function getSize
@treturn number the default text size
*/
static int font_getSize(lua_State *L) {
lua_pushinteger(L, textSize);
return 1;
}
/***
font object
@section Methods
@ -79,6 +106,7 @@ font object
Return the width of a string with a font.
@function :width
@tparam string text the text to test
@tparam[opt=default size] integer font size, in pixels
@treturn number the width of the text (in pixels)
*/
static int font_object_width(lua_State *L) {
@ -95,7 +123,7 @@ static int font_object_width(lua_State *L) {
len = mbstowcs(wtext, text, len);
*(wtext+len) = 0x0; // text end
lua_pushinteger(L, sftd_width_wtext(font->font, size, wtext));
lua_pushinteger(L, sftd_get_wtext_width(font->font, size, wtext));
return 1;
}
@ -127,6 +155,8 @@ static const struct luaL_Reg font_lib[] = {
{ "load", font_load },
{ "setDefault", font_setDefault },
{ "getDefault", font_getDefault },
{ "setSize", font_setSize },
{ "getSize", font_getSize },
{ NULL, NULL }
};
@ -160,4 +190,6 @@ void unload_font_lib(lua_State *L) {
if (luaL_testudata(L, -1, "LFont") != NULL)
sftd_free_font(((font_userdata *)lua_touserdata(L, -1))->font); // Unload current font
lua_pop(L, 1);
}

View file

@ -5,4 +5,6 @@ typedef struct {
sftd_font *font;
} font_userdata;
extern u32 textSize;
#endif

View file

@ -3,25 +3,24 @@ The `fs` module.
@module ctr.fs
@usage local fs = require("ctr.fs")
*/
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <3ds/types.h>
#include <3ds/util/utf.h>
#include <3ds/services/fs.h>
#include <3ds/sdmc.h>
#include <3ds/romfs.h>
#include <lua.h>
#include <lauxlib.h>
bool isFsInitialized = false;
Handle *fsuHandle;
FS_Archive sdmcArchive;
#ifdef ROMFS
FS_Archive romfsArchive;
#endif
/***
The `ctr.fs.lzlib` module.
@table lzlib
@ -40,7 +39,7 @@ const char* prefix_path(const char* path) {
char* prefix = "sdmc:";
#endif
char out[256];
char out[1024];
strcpy(out, prefix);
return strcat(out, path);
@ -53,85 +52,78 @@ const char* prefix_path(const char* path) {
Lists a directory contents (unsorted).
@function list
@tparam string path the directory we wants to list the content
@treturn table the item list. Each item is a table like:
@treturn[1] table the item list. Each item is a table like:
`
{
name = "Item name.txt",
shortName = "ITEM~",
shortExt = "TXT",
isDirectory = false,
isHidden = false,
isArchive = false,
isReadOnly = false,
fileSize = 321 -- (integer) in bytes
size = 321 -- (integer) item size, in bytes
}
`
@treturn[2] nil if an error occurred
@treturn[2] string error message
*/
static int fs_list(lua_State *L) {
const char *path = prefix_path(luaL_checkstring(L, 1));
const char* basepath = prefix_path(luaL_checkstring(L, 1));
char* path;
bool shouldFreePath = false;
if (basepath[strlen(basepath)-1] != '/') {
path = malloc(strlen(basepath)+2);
strcpy(path, basepath);
strcat(path, "/");
shouldFreePath = true;
} else {
path = (char*)basepath;
}
lua_newtable(L);
int i = 1; // table index
// Get default archive
#ifdef ROMFS
FS_Archive archive = romfsArchive;
#else
FS_Archive archive = sdmcArchive;
#endif
// Archive path override (and skip path prefix)
if (strncmp(path, "sdmc:", 5) == 0) {
path += 5;
archive = sdmcArchive;
#ifdef ROMFS
} else if (strncmp(path, "romfs:", 6) == 0) {
path += 6;
archive = romfsArchive;
#endif
DIR* dir = opendir(path);
if (dir == NULL) {
if (shouldFreePath) free(path);
lua_pushnil(L);
lua_pushfstring(L, "Can't open directory: %s (%s)", strerror(errno), errno);
return 2;
}
FS_Path dirPath = fsMakePath(PATH_ASCII, path);
Handle dirHandle;
FSUSER_OpenDirectory(&dirHandle, archive, dirPath);
u32 entriesRead = 0;
do {
FS_DirectoryEntry buffer;
FSDIR_Read(dirHandle, &entriesRead, 1, &buffer);
if (!entriesRead) break;
uint8_t name[0x106+1]; // utf8 file name
size_t size = utf16_to_utf8(name, buffer.name, 0x106);
*(name+size) = 0x0; // mark text end
lua_createtable(L, 0, 8);
lua_pushstring(L, (const char *)name);
errno = 0;
struct dirent *entry;
while (((entry = readdir(dir)) != NULL) && !errno) {
lua_createtable(L, 0, 3);
lua_pushstring(L, (const char*)entry->d_name);
lua_setfield(L, -2, "name");
lua_pushstring(L, (const char *)buffer.shortName);
lua_setfield(L, -2, "shortName");
lua_pushstring(L, (const char *)buffer.shortExt);
lua_setfield(L, -2, "shortExt");
lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_DIRECTORY);
lua_pushboolean(L, entry->d_type==DT_DIR);
lua_setfield(L, -2, "isDirectory");
lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_HIDDEN);
lua_setfield(L, -2, "isHidden");
lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_ARCHIVE);
lua_setfield(L, -2, "isArchive");
lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_READ_ONLY);
lua_setfield(L, -2, "isReadOnly");
lua_pushinteger(L, buffer.fileSize);
lua_setfield(L, -2, "fileSize");
if (entry->d_type==DT_REG) { // Regular files: check size
char* filepath = malloc(strlen(path)+strlen(entry->d_name)+1);
if (filepath == NULL)
luaL_error(L, "Memory allocation error");
strcpy(filepath, path);
strcat(filepath, entry->d_name);
struct stat stats;
if (stat(filepath, &stats)) {
free(filepath);
if (shouldFreePath) free(path);
luaL_error(L, "Stat error: %s (%d)", strerror(errno), errno);
return 0;
} else {
lua_pushinteger(L, stats.st_size);
}
free(filepath);
} else { // Everything else: 0 bytes
lua_pushinteger(L, 0);
}
lua_setfield(L, -2, "size");
lua_seti(L, -2, i);
i++;
} while (entriesRead > 0);
FSDIR_Close(dirHandle);
}
closedir(dir);
if (shouldFreePath) free(path);
return 1;
}
@ -156,9 +148,9 @@ Get the current working directory.
@treturn string the current working directory
*/
static int fs_getDirectory(lua_State *L) {
char cwd[256];
char cwd[1024];
lua_pushstring(L, getcwd(cwd, 256));
lua_pushstring(L, getcwd(cwd, 1024));
return 1;
}
@ -214,16 +206,9 @@ int luaopen_fs_lib(lua_State *L) {
void load_fs_lib(lua_State *L) {
if (!isFsInitialized) {
fsInit();
fsuHandle = fsGetSessionHandle();
FSUSER_Initialize(*fsuHandle);
sdmcArchive = (FS_Archive){ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")};
FSUSER_OpenArchive(&sdmcArchive);
sdmcInit();
#ifdef ROMFS
romfsArchive = (FS_Archive){ARCHIVE_ROMFS, fsMakePath(PATH_EMPTY, "")};
FSUSER_OpenArchive(&romfsArchive);
romfsInit();
#endif
isFsInitialized = true;
}
@ -232,10 +217,8 @@ void load_fs_lib(lua_State *L) {
}
void unload_fs_lib(lua_State *L) {
FSUSER_CloseArchive(&sdmcArchive);
sdmcExit();
#ifdef ROMFS
FSUSER_CloseArchive(&romfsArchive);
romfsExit();
#endif
fsExit();
}

View file

@ -4,21 +4,45 @@ The `gfx` module.
@usage local gfx = require("ctr.gfx")
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sf2d.h>
#include <sftd.h>
#include <3ds/vram.h>
//#include <3ds/vram.h>
//#include <3ds/services/gsp.h>
#include <3ds/console.h>
#include <lua.h>
#include <lauxlib.h>
#include "gfx.h"
#include "font.h"
#include "texture.h"
typedef struct {
sf2d_rendertarget *target;
} target_userdata;
bool isGfxInitialized = false;
bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib.
// The scissor-test state, as defined in Lua code. When you apply a new scissor in C, remember to get back to this state to avoid unexpected behaviour.
scissor_state lua_scissor = {
GPU_SCISSOR_DISABLE,
0, 0,
0, 0
};
// Rotate a point (x,y) around the center (cx,cy) by angle radians.
void rotatePoint(int x, int y, int cx, int cy, float angle, int* outx, int* outy) {
float s = sin(angle), c = cos(angle);
int tx = x - cx, ty = y - cy;
*outx = round(tx * c - ty * s) + cx;
*outy = round(tx * s + ty * c) + cy;
}
/***
The `ctr.gfx.color` module.
@table color
@ -50,17 +74,23 @@ The `ctr.gfx.map` module.
void load_map_lib(lua_State *L);
/***
Start drawing to a screen.
Start drawing to a screen/target.
Must be called before any draw operation.
@function start
@tparam number screen the screen to draw to (`gfx.TOP` or `gfx.BOTTOM`)
@tparam number/target screen the screen or target to draw to (`gfx.TOP`, `gfx.BOTTOM`, or render target)
@tparam[opt=gfx.LEFT] number eye the eye to draw to (`gfx.LEFT` or `gfx.RIGHT`)
*/
static int gfx_start(lua_State *L) {
u8 screen = luaL_checkinteger(L, 1);
u8 eye = luaL_optinteger(L, 2, GFX_LEFT);
if (lua_isinteger(L, 1)) {
u8 screen = luaL_checkinteger(L, 1);
u8 eye = luaL_optinteger(L, 2, GFX_LEFT);
sf2d_start_frame(screen, eye);
sf2d_start_frame(screen, eye);
} else if (lua_isuserdata(L, 1)) {
target_userdata *target = luaL_checkudata(L, 1, "LTarget");
sf2d_start_frame_target(target->target);
}
return 0;
}
@ -162,28 +192,6 @@ static int gfx_vramSpaceFree(lua_State *L) {
return 1;
}
/***
Draw a line on the current screen.
@function line
@tparam integer x1 line's starting point horizontal coordinate, in pixels
@tparam integer y1 line's starting point vertical coordinate, in pixels
@tparam integer x2 line's endpoint horizontal coordinate, in pixels
@tparam integer y2 line's endpoint vertical coordinate, in pixels
@tparam[opt=default color] integer color drawing color
*/
static int gfx_line(lua_State *L) {
int x1 = luaL_checkinteger(L, 1);
int y1 = luaL_checkinteger(L, 2);
int x2 = luaL_checkinteger(L, 3);
int y2 = luaL_checkinteger(L, 4);
u32 color = luaL_optinteger(L, 5, color_default);
sf2d_draw_line(x1, y1, x2, y2, color);
return 0;
}
/***
Draw a point, a single pixel, on the current screen.
@function point
@ -194,16 +202,96 @@ Draw a point, a single pixel, on the current screen.
static int gfx_point(lua_State *L) {
int x = luaL_checkinteger(L, 1);
int y = luaL_checkinteger(L, 2);
u32 color = luaL_optinteger(L, 3, color_default);
sf2d_draw_rectangle(x, y, 1, 1, color); // well, it looks like a point
return 0;
}
/***
Draw a rectangle on the current screen.
Draw a line on the current screen.
@function line
@tparam integer x1 line's starting point horizontal coordinate, in pixels
@tparam integer y1 line's starting point vertical coordinate, in pixels
@tparam integer x2 line's endpoint horizontal coordinate, in pixels
@tparam integer y2 line's endpoint vertical coordinate, in pixels
@tparam[opt=1] number width line's thickness, in pixels
@tparam[opt=default color] integer color drawing color
*/
static int gfx_line(lua_State *L) {
int x1 = luaL_checkinteger(L, 1);
int y1 = luaL_checkinteger(L, 2);
int x2 = luaL_checkinteger(L, 3);
int y2 = luaL_checkinteger(L, 4);
float width = luaL_optnumber(L, 5, 1.0f);
u32 color = luaL_optinteger(L, 6, color_default);
sf2d_draw_line(x1, y1, x2, y2, width, color);
return 0;
}
/***
Draw a filled triangle on the current screen.
@function triangle
@tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels
@tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels
@tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels
@tparam integer y2 vertical coordinate of a vertex of the triangle, in pixels
@tparam integer x3 horizontal coordinate of a vertex of the triangle, in pixels
@tparam integer y3 vertical coordinate of a vertex of the triangle, in pixels
@tparam[opt=default color] integer color drawing color
*/
static int gfx_triangle(lua_State *L) {
int x1 = luaL_checkinteger(L, 1);
int y1 = luaL_checkinteger(L, 2);
int x2 = luaL_checkinteger(L, 3);
int y2 = luaL_checkinteger(L, 4);
int x3 = luaL_checkinteger(L, 5);
int y3 = luaL_checkinteger(L, 6);
u32 color = luaL_optinteger(L, 7, color_default);
sf2d_draw_triangle(x1, y1, x2, y2, x3, y3, color);
return 0;
}
/***
Draw a triangle outline on the current screen.
@function linedTriangle
@tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels
@tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels
@tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels
@tparam integer y2 vertical coordinate of a vertex of the triangle, in pixels
@tparam integer x3 horizontal coordinate of a vertex of the triangle, in pixels
@tparam integer y3 vertical coordinate of a vertex of the triangle, in pixels
@tparam[opt=1] number lineWidth line's thickness, in pixels
@tparam[opt=default color] integer color drawing color
*/
static int gfx_linedTriangle(lua_State *L) {
int x1 = luaL_checkinteger(L, 1);
int y1 = luaL_checkinteger(L, 2);
int x2 = luaL_checkinteger(L, 3);
int y2 = luaL_checkinteger(L, 4);
int x3 = luaL_checkinteger(L, 5);
int y3 = luaL_checkinteger(L, 6);
float lineWidth = luaL_optnumber(L, 7, 1.0f);
u32 color = luaL_optinteger(L, 8, color_default);
sf2d_draw_line(x1, y1, x2, y2, lineWidth, color);
sf2d_draw_line(x2, y2, x3, y3, lineWidth, color);
sf2d_draw_line(x3, y3, x1, y1, lineWidth, color);
return 0;
}
/***
Draw a filled rectangle on the current screen.
@function rectangle
@tparam integer x rectangle origin horizontal coordinate, in pixels
@tparam integer y rectangle origin vertical coordinate, in pixels
@ -211,6 +299,8 @@ Draw a rectangle on the current screen.
@tparam integer height rectangle height, in pixels
@tparam[opt=0] number angle rectangle rotation, in radians
@tparam[opt=default color] integer color drawing color
@tparam[opt] integer color2 Second drawing color ; if the argument is not nil, the rectangle will be filled with a gradient from color to color2
@tparam[opt] integer direction Gradient drawing direction (`gfx.TOP_TO_BOTTOM` or `gfx.LEFT_TO_RIGHT`). This argument is mandatory if a second color was specified.
*/
static int gfx_rectangle(lua_State *L) {
int x = luaL_checkinteger(L, 1);
@ -220,17 +310,73 @@ static int gfx_rectangle(lua_State *L) {
float angle = luaL_optnumber(L, 5, 0);
u32 color = luaL_optinteger(L, 6, color_default);
if (angle == 0)
sf2d_draw_rectangle(x, y, width, height, color);
else
sf2d_draw_rectangle_rotate(x, y, width, height, color, angle);
// Not second color : fill with plain color.
if (lua_isnoneornil(L, 7)) {
if (angle == 0)
sf2d_draw_rectangle(x, y, width, height, color);
else
sf2d_draw_rectangle_rotate(x, y, width, height, color, angle);
// Two colors : fill with a gradient.
} else {
u32 color2 = luaL_checkinteger(L, 7);
u8 direction = luaL_checkinteger(L, 8);
if (angle == 0)
sf2d_draw_rectangle_gradient(x, y, width, height, color, color2, direction);
else
sf2d_draw_rectangle_gradient_rotate(x, y, width, height, color, color2, direction, angle);
}
return 0;
}
/***
Draw a circle on the current screen.
Draw a rectangle outline on the current screen.
@function linedRectangle
@tparam integer x rectangle origin horizontal coordinate, in pixels
@tparam integer y rectangle origin vertical coordinate, in pixels
@tparam integer width rectangle width, in pixels
@tparam integer height rectangle height, in pixels
@tparam[opt=1] integer lineWidth line's thickness, in pixels
@tparam[opt=0] number angle rectangle rotation, in radians
@tparam[opt=default color] integer color drawing color
*/
static int gfx_linedRectangle(lua_State *L) {
int x = luaL_checkinteger(L, 1);
int y = luaL_checkinteger(L, 2);
int width = luaL_checkinteger(L, 3);
int height = luaL_checkinteger(L, 4);
float lineWidth = luaL_optnumber(L, 5, 1.0f);
float angle = luaL_optnumber(L, 6, 0);
u32 color = luaL_optinteger(L, 7, color_default);
// Corner coordinates
int x2 = x + width, y2 = y;
int x3 = x2, y3 = y + height;
int x4 = x, y4 = y3;
// Rotate corners
if (angle != 0) {
int cx = x + width/2, cy = y + height/2;
rotatePoint(x, y, cx, cy, angle, &x, &y );
rotatePoint(x2, y2, cx, cy, angle, &x2, &y2);
rotatePoint(x3, y3, cx, cy, angle, &x3, &y3);
rotatePoint(x4, y4, cx, cy, angle, &x4, &y4);
}
// Draw lines
sf2d_draw_line(x, y, x2, y2, lineWidth, color);
sf2d_draw_line(x2, y2, x3, y3, lineWidth, color);
sf2d_draw_line(x3, y3, x4, y4, lineWidth, color);
sf2d_draw_line(x4, y4, x, y, lineWidth, color);
return 0;
}
/***
Draw a filled circle on the current screen.
@function circle
@tparam integer x circle center horizontal coordinate, in pixels
@tparam integer y circle center vertical coordinate, in pixels
@ -249,13 +395,63 @@ static int gfx_circle(lua_State *L) {
return 0;
}
/***
Draw a circle outline on the current screen.
@function linedCircle
@tparam integer x circle center horizontal coordinate, in pixels
@tparam integer y circle center vertical coordinate, in pixels
@tparam integer radius circle radius, in pixels
@tparam[opt=1] integer width line's thickness, in pixels
@tparam[opt=default color] integer color drawing color
*/
static int gfx_linedCircle(lua_State *L) {
int x0 = luaL_checkinteger(L, 1);
int y0 = luaL_checkinteger(L, 2);
int radius = luaL_checkinteger(L, 3);
float width = luaL_optnumber(L, 4, 1.0f);
u32 color = luaL_optinteger(L, 5, color_default);
for (int r = ceil(radius - width/2), maxr = ceil(radius + width/2)-1; r <= maxr; r++) {
// Implementatin of the Andres circle algorithm.
int x = 0;
int y = r;
int d = r - 1;
while (y >= x) {
// Best way to draw a lot of points, 10/10
sf2d_draw_rectangle(x0 + x , y0 + y, 1, 1, color);
sf2d_draw_rectangle(x0 + y , y0 + x, 1, 1, color);
sf2d_draw_rectangle(x0 - x , y0 + y, 1, 1, color);
sf2d_draw_rectangle(x0 - y , y0 + x, 1, 1, color);
sf2d_draw_rectangle(x0 + x , y0 - y, 1, 1, color);
sf2d_draw_rectangle(x0 + y , y0 - x, 1, 1, color);
sf2d_draw_rectangle(x0 - x , y0 - y, 1, 1, color);
sf2d_draw_rectangle(x0 - y , y0 - x, 1, 1, color);
if (d >= 2*x) {
d -= 2*x + 1;
x++;
} else if (d < 2*(r-y)) {
d += 2*y - 1;
y--;
} else {
d += 2*(y - x - 1);
y--;
x++;
}
}
}
return 0;
}
/***
Draw a text on the current screen.
@function text
@tparam integer x text drawing origin horizontal coordinate, in pixels
@tparam integer y text drawing origin vertical coordinate, in pixels
@tparam string text the text to draw
@tparam[opt=9] integer size drawing size, in pixels
@tparam[opt=default size] integer size drawing size, in pixels
@tparam[opt=default color] integer color drawing color
@tparam[opt=default font] font font to use
*/
@ -265,7 +461,7 @@ static int gfx_text(lua_State *L) {
size_t len;
const char *text = luaL_checklstring(L, 3, &len);
int size = luaL_optinteger(L, 4, 9);
int size = luaL_optinteger(L, 4, textSize);
u32 color = luaL_optinteger(L, 5, color_default);
font_userdata *font = luaL_testudata(L, 6, "LFont");
if (font == NULL) {
@ -293,7 +489,7 @@ Warning: No UTF32 support.
@tparam integer y text drawing origin vertical coordinate, in pixels
@tparam string text the text to draw
@tparam integer width width of a line, in pixels
@tparam[opt=9] integer size drawing size, in pixels
@tparam[opt=default Size] integer size drawing size, in pixels
@tparam[opt=default color] integer color drawing color
@tparam[opt=default font] font font to use
*/
@ -304,7 +500,7 @@ static int gfx_wrappedText(lua_State *L) {
const char *text = luaL_checklstring(L, 3, &len);
unsigned int lineWidth = luaL_checkinteger(L, 4);
int size = luaL_optinteger(L, 5, 9);
int size = luaL_optinteger(L, 5, textSize);
u32 color = luaL_optinteger(L, 6, color_default);
font_userdata *font = luaL_testudata(L, 7, "LFont");
if (font == NULL) {
@ -330,7 +526,7 @@ Calculate the size of a text draw with `wrappedText`.
@function calcBoundingBox
@tparam string text The text to check
@tparam integer lineWidth width of a line, in pixels
@tparam[opt=9] integer size drawing size, in pixels
@tparam[opt=default size] integer size drawing size, in pixels
@tparam[opt=default font] font font to use
@treturn integer width of the text, in pixels
@treturn integer height of the text, in pixels
@ -339,7 +535,7 @@ static int gfx_calcBoundingBox(lua_State *L) {
size_t len;
const char *text = luaL_checklstring(L, 1, &len);
unsigned int lineWidth = luaL_checkinteger(L, 2);
int size = luaL_optinteger(L, 3, 9);
int size = luaL_optinteger(L, 3, textSize);
font_userdata *font = luaL_testudata(L, 4, "LFont");
if (font == NULL) {
lua_getfield(L, LUA_REGISTRYINDEX, "LFontDefault");
@ -356,6 +552,192 @@ static int gfx_calcBoundingBox(lua_State *L) {
return 2;
}
/***
Enables or disable the scissor test.
When the scissor test is enabled, the drawing area will be limited to a specific rectangle, every pixel drawn outside will be discarded.
Calls this function without argument to disable the scissor test.
@function scissor
@tparam integer x scissor rectangle origin horizontal coordinate, in pixels
@tparam integer y scissor rectangle origin vertical coordinate, in pixels
@tparam integer width scissor rectangle width, in pixels
@tparam integer height scissor rectangle height, in pixels
@tparam[opt=false] boolean invert if true the scissor will be inverted (will draw only outside of the rectangle)
*/
static int gfx_scissor(lua_State *L) {
if (lua_gettop(L) == 0) {
lua_scissor.mode = GPU_SCISSOR_DISABLE;
lua_scissor.x = 0;
lua_scissor.y = 0;
lua_scissor.width = 0;
lua_scissor.height = 0;
} else {
lua_scissor.x = luaL_checkinteger(L, 1);
lua_scissor.y = luaL_checkinteger(L, 2);
lua_scissor.width = luaL_checkinteger(L, 3);
lua_scissor.height = luaL_checkinteger(L, 4);
lua_scissor.mode = lua_toboolean(L, 5) ? GPU_SCISSOR_INVERT : GPU_SCISSOR_NORMAL;
}
sf2d_set_scissor_test(lua_scissor.mode, lua_scissor.x, lua_scissor.y, lua_scissor.width, lua_scissor.height);
return 0;
}
/***
__Work in progress__. Create a render target. Don't use it.
@function target
@tparam integer width
@tparam integer height
@treturn target
*/
static int gfx_target(lua_State *L) {
int width = luaL_checkinteger(L, 1);
int height = luaL_checkinteger(L, 2);
int wpo2 = 0, hpo2 = 0;
for (;width>pow(2,wpo2);wpo2++);
width = pow(2,wpo2);
for (;height>pow(2,hpo2);hpo2++);
height = pow(2,hpo2);
target_userdata *target;
target = (target_userdata*)lua_newuserdata(L, sizeof(*target));
luaL_getmetatable(L, "LTarget");
lua_setmetatable(L, -2);
target->target = sf2d_create_rendertarget(width, height);
return 1;
}
/***
Render targets
@section target
*/
/***
Clear a target to a specified color.
@function :clear
@tparam[opt=default color] integer color color to fill the target with
*/
static int gfx_target_clear(lua_State *L) {
target_userdata *target = luaL_checkudata(L, 1, "LTarget");
u32 color = luaL_optinteger(L, 2, color_default);
sf2d_clear_target(target->target, color);
return 0;
}
/***
Destroy a target.
@function :destroy
*/
static int gfx_target_destroy(lua_State *L) {
target_userdata *target = luaL_checkudata(L, 1, "LTarget");
sf2d_free_target(target->target);
return 0;
}
static const struct luaL_Reg target_methods[];
/***
*/
static int gfx_target___index(lua_State *L) {
target_userdata *target = luaL_checkudata(L, 1, "LTarget");
const char* name = luaL_checkstring(L, 2);
if (strcmp(name, "texture") == 0) {
texture_userdata *texture;
texture = (texture_userdata*)lua_newuserdata(L, sizeof(*texture));
luaL_getmetatable(L, "LTexture");
lua_setmetatable(L, -2);
texture->texture = &(target->target->texture);
texture->scaleX = 1.0f;
texture->scaleY = 1.0f;
texture->blendColor = 0xffffffff;
return 1;
} else if (strcmp(name, "duck") == 0) {
sf2d_rendertarget *target = sf2d_create_rendertarget(64, 64);
for(int i=0;;i++) {
sf2d_clear_target(target, 0xff000000);
sf2d_start_frame_target(target);
sf2d_draw_fill_circle(i%380, i%200, 10, 0xff0000ff);
sf2d_end_frame();
//sf2d_texture_tile32(&target->texture);
sf2d_start_frame(GFX_TOP, GFX_LEFT);
sf2d_draw_texture(&target->texture, 10, 10);
sf2d_end_frame();
sf2d_swapbuffers();
}
} else {
for (u8 i=0;target_methods[i].name;i++) {
if (strcmp(target_methods[i].name, name) == 0) {
lua_pushcfunction(L, target_methods[i].func);
return 1;
}
}
}
lua_pushnil(L);
return 1;
}
/***
Console
@section console
*/
/***
Initialize the console. You can print on it using print(), or any function that normally outputs to stdout.
Warning: you can't use a screen for both a console and drawing, you have to disable the console first.
@function console
@tparam[opt=gfx.TOP] number screen screen to draw the console on.
@tparam[opt=false] boolean debug enable stderr output on the console
*/
u8 consoleScreen = GFX_TOP;
static int gfx_console(lua_State *L) {
consoleScreen = luaL_optinteger(L, 1, GFX_TOP);
bool err = false;
if (lua_isboolean(L, 2)) {
err = lua_toboolean(L, 2);
}
consoleInit(consoleScreen, NULL);
if (err)
consoleDebugInit(debugDevice_CONSOLE);
return 0;
}
/***
Clear the console.
@function clearConsole
*/
static int gfx_clearConsole(lua_State *L) {
consoleClear();
return 0;
}
/***
Disable the console.
@function disableConsole
*/
static int gfx_disableConsole(lua_State *L) {
gfxSetScreenFormat(consoleScreen, GSP_BGR8_OES);
gfxSetDoubleBuffering(consoleScreen, true);
gfxSwapBuffersGpu();
gspWaitForVBlank();
return 0;
}
// Functions
static const struct luaL_Reg gfx_lib[] = {
{ "start", gfx_start },
@ -367,66 +749,97 @@ static const struct luaL_Reg gfx_lib[] = {
{ "setVBlankWait", gfx_setVBlankWait },
{ "waitForVBlank", gfx_waitForVBlank },
{ "vramSpaceFree", gfx_vramSpaceFree },
{ "line", gfx_line },
{ "point", gfx_point },
{ "line", gfx_line },
{ "triangle", gfx_triangle },
{ "linedTriangle", gfx_linedTriangle },
{ "rectangle", gfx_rectangle },
{ "linedRectangle", gfx_linedRectangle },
{ "circle", gfx_circle },
{ "linedCircle", gfx_linedCircle },
{ "text", gfx_text },
{ "wrappedText", gfx_wrappedText },
{ "calcBoundingBox", gfx_calcBoundingBox },
{ "scissor", gfx_scissor },
{ "target", gfx_target },
{ "console", gfx_console },
{ "clearConsole", gfx_clearConsole },
{ "disableConsole", gfx_disableConsole },
{ NULL, NULL }
};
// Constants
// Render target
static const struct luaL_Reg target_methods[] = {
{ "__index", gfx_target___index },
{ "clear", gfx_target_clear },
{ "destroy", gfx_target_destroy },
{ "__gc", gfx_target_destroy },
{ NULL, NULL }
};
/***
Constants
@section constants
*/
struct { char *name; int value; } gfx_constants[] = {
/***
Constant used to select the top screen.
It is equal to `0`.
@field TOP
*/
{ "TOP", GFX_TOP },
{ "TOP", GFX_TOP },
/***
Constant used to select the bottom screen.
It is equal to `1`.
@field BOTTOM
*/
{ "BOTTOM", GFX_BOTTOM },
{ "BOTTOM", GFX_BOTTOM },
/***
Constant used to select the left eye.
It is equal to `0`.
@field LEFT
*/
{ "LEFT", GFX_LEFT },
{ "LEFT", GFX_LEFT },
/***
Constant used to select the right eye.
It is equal to `1`.
@field RIGHT
*/
{ "RIGHT", GFX_RIGHT },
{ "RIGHT", GFX_RIGHT },
/***
The top screen height, in pixels.
It is equal to `240`.
@field TOP_HEIGHT
*/
{ "TOP_HEIGHT", 240 },
{ "TOP_HEIGHT", 240 },
/***
The top screen width, in pixels.
It is equal to `400`.
@field TOP_WIDTH
*/
{ "TOP_WIDTH", 400 },
{ "TOP_WIDTH", 400 },
/***
The bottom screen height, in pixels.
It is equal to `240`.
@field BOTTOM_HEIGHT
*/
{ "BOTTOM_HEIGHT", 240 },
{ "BOTTOM_HEIGHT", 240 },
/***
The bottom screen width, in pixels.
It is equal to `320`.
@field BOTTOM_WIDTH
*/
{ "BOTTOM_WIDTH", 320 },
{ "BOTTOM_WIDTH", 320 },
/***
Represents a vertical gradient drawn from the top (first color) to the bottom (second color).
@field TOP_TO_BOTTOM
*/
{ "TOP_TO_BOTTOM", SF2D_TOP_TO_BOTTOM },
/***
Represents a horizontal gradient drawn from the left (first color) to the right (second color).
@field LEFT_TO_RIGHT
*/
{ "LEFT_TO_RIGHT", SF2D_LEFT_TO_RIGHT },
{ NULL, 0 }
};
@ -440,6 +853,11 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); }
};
int luaopen_gfx_lib(lua_State *L) {
luaL_newmetatable(L, "LTarget");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, target_methods, 0);
luaL_newlib(L, gfx_lib);
for (int i = 0; gfx_constants[i].name; i++) {

12
source/gfx.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef GFX_H
#define GFX_H
typedef struct {
GPU_SCISSORMODE mode;
u32 x; u32 y;
u32 width; u32 height;
} scissor_state;
extern scissor_state lua_scissor;
#endif

View file

@ -1,11 +1,12 @@
/***
The `hid` module.
The circle pad pro is supported, it's keys replace de "3ds only" keys
The circle pad pro is supported, it's keys replace the "3ds only" keys
@module ctr.hid
@usage local hid = require("ctr.hid")
*/
#include <3ds/types.h>
#include <3ds/services/hid.h>
#include <3ds/services/irrst.h>
#include <lua.h>
#include <lauxlib.h>
@ -175,6 +176,25 @@ static int hid_circle(lua_State *L) {
return 2;
}
/***
Return the C-stick position.
`0,0` is the center position, and the stick should return to it if not touched (95% of the time).
Range is from `-146` to `146` on both X and Y, but these are hard to reach.
@newonly
@function cstick
@treturn number X position
@treturn number Y position
*/
static int hid_cstick(lua_State *L) {
circlePosition pos;
irrstCstickRead(&pos);
lua_pushinteger(L, pos.dx);
lua_pushinteger(L, pos.dy);
return 2;
}
/***
Return the accelerometer vector
@function accel
@ -243,6 +263,7 @@ static const struct luaL_Reg hid_lib[] = {
{ "keys", hid_keys },
{ "touch", hid_touch },
{ "circle", hid_circle },
{ "cstick", hid_cstick },
{ "accel", hid_accel },
{ "gyro", hid_gyro },
{ "volume", hid_volume },

View file

@ -9,6 +9,7 @@ The `httpc` module.
#include <3ds.h>
#include <3ds/types.h>
#include <3ds/services/httpc.h>
#include <3ds/services/sslc.h>
#include <lapi.h>
#include <lauxlib.h>
@ -22,12 +23,6 @@ Create a HTTP Context.
*/
static int httpc_context(lua_State *L) {
httpcContext context;
Result ret = httpcOpenContext(&context, "http://google.com/", 0); // Initialization only.
if (ret != 0) {
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
lua_newuserdata(L, sizeof(&context));
luaL_getmetatable(L, "LHTTPC");
lua_setmetatable(L, -2);
@ -44,15 +39,30 @@ context object
Open an url in the context.
@function :open
@tparam string url the url to open
@tparam[opt="GET"] string method method to use; can be `"GET"`, `"POST"`, `"HEAD"`, `"PUT"` or `"DELETE"`
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int httpc_open(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
char *url = (char*)luaL_checkstring(L, 2);
char *smethod = (char*)luaL_optstring(L, 3, "GET");
HTTPC_RequestMethod method = HTTPC_METHOD_GET; // default to GET
if (strcmp(smethod, "POST")) {
method = HTTPC_METHOD_POST;
} else if (strcmp(smethod, "HEAD")) {
method = HTTPC_METHOD_HEAD;
} else if (strcmp(smethod, "PUT")) {
method = HTTPC_METHOD_PUT;
} else if (strcmp(smethod, "DELETE")) {
method = HTTPC_METHOD_DELETE;
}
Result ret = 0;
ret = httpcOpenContext(context, url, 0);
ret = httpcOpenContext(context, method, url, 0);
if (ret != 0) {
lua_pushnil(L);
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
@ -65,6 +75,9 @@ Add a field in the request header.
@function :addRequestHeaderField
@tparam string name Name of the field
@tparam string value Value of the field
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int httpc_addRequestHeaderField(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
@ -73,7 +86,7 @@ static int httpc_addRequestHeaderField(lua_State *L) {
Result ret = httpcAddRequestHeaderField(context, name ,value);
if (ret != 0) {
lua_pushnil(L);
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
@ -84,6 +97,9 @@ static int httpc_addRequestHeaderField(lua_State *L) {
/***
Begin a request to get the content at the URL.
@function :beginRequest
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int httpc_beginRequest(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
@ -91,7 +107,7 @@ static int httpc_beginRequest(lua_State *L) {
ret = httpcBeginRequest(context);
if (ret != 0) {
lua_pushnil(L);
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
@ -102,7 +118,9 @@ static int httpc_beginRequest(lua_State *L) {
/***
Return the status code returned by the request.
@function :getStatusCode
@treturn number the status code
@treturn[1] integer the status code
@treturn[2] nil in case of error
@treturn[2] integer error code
*/
static int httpc_getStatusCode(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
@ -136,7 +154,9 @@ static int httpc_getDownloadSize(lua_State *L) {
/***
Download and return the data of the context.
@function :downloadData
@treturn string data
@treturn[1] string data
@treturn[2] nil in case of error
@treturn[2] integer error code
*/
static int httpc_downloadData(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
@ -154,15 +174,16 @@ static int httpc_downloadData(lua_State *L) {
ret = httpcDownloadData(context, buff, size, NULL);
if (ret != 0) {
free(buff);
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
lua_pushstring(L, (char*)buff);
//free(buff);
lua_pushinteger(L, size); // only for test purposes.
return 2;
free(buff);
//lua_pushinteger(L, size); // only for test purposes.
return 1;
}
/***
@ -177,6 +198,100 @@ static int httpc_close(lua_State *L) {
return 0;
}
/***
Add a POST form field to a HTTP context.
@function :addPostData
@tparam string name name of the field
@tparam string value value of the field
*/
static int httpc_addPostData(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
char *name = (char*)luaL_checkstring(L, 2);
char *value = (char*)luaL_checkstring(L, 3);
httpcAddPostDataAscii(context, name, value);
return 0;
}
/***
Get a header field from a response.
@function :getResponseHeader
@tparam string name name of the header field to get
@tparam[opt=2048] number maximum size of the value to get
@treturn string field value
*/
static int httpc_getResponseHeader(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
char *name = (char*)luaL_checkstring(L, 2);
u32 maxSize = luaL_checkinteger(L, 3);
char* value = 0;
httpcGetResponseHeader(context, name, value, maxSize);
lua_pushstring(L, value);
return 1;
}
/***
Add a trusted RootCA cert to a context.
@function :addTrustedRootCA
@tparam string DER certificate
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int httpc_addTrustedRootCA(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
u32 certsize;
u8* cert = (u8*)luaL_checklstring(L, 2, (size_t*)&certsize);
Result ret = httpcAddTrustedRootCA(context, cert, certsize);
if (ret != 0) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
lua_pushboolean(L, true);
return 1;
}
/***
Set SSL options for a context.
@function :setSSLOptions
@tparam boolean disableVerify disable server certificate verification if `true`
@tparam[opt=false] boolean tlsv10 use TLS v1.0 if `true`
*/
static int httpc_setSSLOptions(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
bool disVer = lua_toboolean(L, 2);
bool tsl10 = false;
if (lua_isboolean(L, 3))
tsl10 = lua_toboolean(L, 3);
httpcSetSSLOpt(context, (disVer?SSLCOPT_DisableVerify:0)|(tsl10?SSLCOPT_TLSv10:0));
return 0;
}
/***
Add all the default certificates to the context.
@function addDefaultCert
*/
static int httpc_addDefaultCert(lua_State *L) {
httpcContext *context = lua_touserdata(L, 1);
httpcAddDefaultCert(context, SSLC_DefaultRootCert_CyberTrust);
httpcAddDefaultCert(context, SSLC_DefaultRootCert_AddTrust_External_CA);
httpcAddDefaultCert(context, SSLC_DefaultRootCert_COMODO);
httpcAddDefaultCert(context, SSLC_DefaultRootCert_USERTrust);
httpcAddDefaultCert(context, SSLC_DefaultRootCert_DigiCert_EV);
return 0;
}
// object
static const struct luaL_Reg httpc_methods[] = {
{"open", httpc_open },
@ -186,6 +301,12 @@ static const struct luaL_Reg httpc_methods[] = {
{"getDownloadSize", httpc_getDownloadSize },
{"downloadData", httpc_downloadData },
{"close", httpc_close },
{"__gc", httpc_close },
{"addPostData", httpc_addPostData },
{"getResponseHeader", httpc_getResponseHeader },
{"addTrustedRootCA", httpc_addTrustedRootCA },
{"setSSLOptions", httpc_setSSLOptions },
{"addDefaultCert", httpc_addDefaultCert },
{NULL, NULL}
};
@ -208,7 +329,7 @@ int luaopen_httpc_lib(lua_State *L) {
void load_httpc_lib(lua_State *L) {
if (!isHttpcInitialized) {
httpcInit();
httpcInit(0x1000);
isHttpcInitialized = true;
}

View file

@ -5,7 +5,7 @@ The `ir` module.
*/
#include <3ds/types.h>
#include <3ds/services/ir.h>
#include <3ds/linear.h>
//#include <3ds/linear.h>
#include <lualib.h>
#include <lauxlib.h>
@ -37,6 +37,9 @@ Bitrate codes list (this is not a part of the module, just a reference)
Initialize the IR module.
@function init
@tparam[opt=6] number bitrate bitrate of the IR module (more informations below)
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int ir_init(lua_State *L) {
u8 bitrate = luaL_optinteger(L, 1, 6);
@ -56,6 +59,9 @@ static int ir_init(lua_State *L) {
/***
Disable the IR module.
@function shutdown
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int ir_shutdown(lua_State *L) {
Result ret = IRU_Shutdown();
@ -74,6 +80,9 @@ Send some data over the IR module.
@function send
@tparam string data just some data
@tparam[opt=false] boolean wait set to `true` to wait until the data is sent.
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int ir_send(lua_State *L) {
u8 *data = (u8*)luaL_checkstring(L, 1);
@ -98,7 +107,9 @@ Receive some data from the IR module.
@function receive
@tparam number size bytes to receive
@tparam[opt=false] boolean wait wait until the data is received
@return string data
@treturn[1] string data
@treturn[2] nil in case of error
@treturn[2] integer error code
*/
static int ir_receive(lua_State *L) {
u32 size = luaL_checkinteger(L, 1);
@ -108,7 +119,7 @@ static int ir_receive(lua_State *L) {
Result ret = iruRecvData(data, size, 0x00, &transfercount, wait);
if (ret) {
lua_pushboolean(L, false);
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
@ -122,6 +133,9 @@ static int ir_receive(lua_State *L) {
Set the bitrate of the communication.
@function setBitRate
@tparam number bitrate new bitrate for the communication
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int ir_setBitRate(lua_State *L) {
u8 bitrate = luaL_checkinteger(L, 1);
@ -133,20 +147,24 @@ static int ir_setBitRate(lua_State *L) {
return 2;
}
return 0;
lua_pushboolean(L, true);
return 1;
}
/***
Return the actual bitrate of the communication.
@function getBitRate
@treturn number actual bitrate
@treturn[1] number actual bitrate
@treturn[2] nil in case of error
@treturn[2] integer error code
*/
static int ir_getBitRate(lua_State *L) {
u8 bitrate = 0;
Result ret = IRU_GetBitRate(&bitrate);
if (ret) {
lua_pushboolean(L, false);
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}

View file

@ -1,3 +1,5 @@
#include <unistd.h>
#include <3ds.h>
#include <lua.h>
@ -32,7 +34,14 @@ void error(const char *error) {
}
// Main loop
int main() {
int main(int argc, char** argv) {
// Default arguments
#ifdef ROMFS
char* mainFile = "romfs:/main.lua";
#else
char* mainFile = "main.lua";
#endif
// Init Lua
lua_State *L = luaL_newstate();
if (L == NULL) {
@ -43,15 +52,38 @@ int main() {
// Load libs
luaL_openlibs(L);
load_ctr_lib(L);
isGfxInitialized = true;
// Parse arguments
for (int i=0;i<argc;i++) {
if (argv[i][0] == '-') {
switch(argv[i][1]) {
case 'm': { // main file replacement
mainFile = &argv[i][2];
if (argv[i][2] == ' ') mainFile = &argv[i][3];
break;
}
case 'r': { // root directory replacement
char* root;
root = &argv[i][2];
if (argv[i][2] == ' ') root = &argv[i][3];
if (chdir(root)) error("No such root path");
break;
}
}
}
}
// Do the actual thing
if (luaL_dofile(L, "main.lua")) error(luaL_checkstring(L, -1));
if (luaL_dofile(L, mainFile)) error(luaL_checkstring(L, -1));
// Unload libs
unload_ctr_lib(L);
// Unload Lua
lua_close(L);
return 0;
}

View file

@ -1,5 +1,5 @@
/***
The `map` module.
The `gfx.map` module.
Tile coordinates start at x=0,y=0.
@module ctr.gfx.map
@usage local map = require("ctr.gfx.map")
@ -13,7 +13,9 @@ Tile coordinates start at x=0,y=0.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "gfx.h"
#include "texture.h"
typedef struct {
@ -47,7 +49,9 @@ Load a map from a file.
@tparam texture tileset containing the tileset
@tparam number tileWidth tile width
@tparam number tileHeight tile height
@treturn map loaded map object
@treturn[1] map loaded map object
@treturn[2] nil in case of error
@treturn[2] string error message
*/
static int map_load(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 2, "LTexture");
@ -57,7 +61,16 @@ static int map_load(lua_State *L) {
map_userdata *map = lua_newuserdata(L, sizeof(map_userdata));
luaL_getmetatable(L, "LMap");
lua_setmetatable(L, -2);
// Block GC of the texture by keeping a reference to it in the registry
// registry[map_userdata] = texture_userdata
lua_pushnil(L);
lua_copy(L, -2, -1); // map_userdata
lua_pushnil(L);
lua_copy(L, 2, -1); // texture_userdata
lua_settable(L, LUA_REGISTRYINDEX);
// Init userdata fields
map->texture = texture;
map->tileSizeX = tileSizeX;
map->tileSizeY = tileSizeY;
@ -156,50 +169,81 @@ Map object
*/
/***
Draw a map.
Draw (a part of) the map on the screen.
@function :draw
@tparam number x X position
@tparam number y Y position
@within Methods
@tparam integer x X top-left coordinate to draw the map on the screen (pixels)
@tparam integer y Y top-left coordinate to draw the map on the screen (pixels)
@tparam[opt=0] integer offsetX drawn area X start coordinate on the map (pixels) (x=0,y=0 correspond to the first tile top-left corner)
@tparam[opt=0] integer offsetY drawn area Y start coordinate on the map (pixels)
@tparam[opt=400] integer width width of the drawn area on the map (pixels)
@tparam[opt=240] integer height height of the drawn area on the map (pixels)
@usage
-- This will draw on the screen at x=5,y=5 a part of the map. The part is the rectangle on the map starting at x=16,y=16 and width=32,height=48.
-- For example, if you use 16x16 pixel tiles, this will draw the tiles from 1,1 (top-left corner of the rectangle) to 2,3 (bottom-right corner).
map:draw(5, 5, 16, 16, 32, 48)
*/
static int map_draw(lua_State *L) {
map_userdata *map = luaL_checkudata(L, 1, "LMap");
int x = luaL_checkinteger(L, 2);
int y = luaL_checkinteger(L, 3);
int offsetX = luaL_optinteger(L, 4, 0);
int offsetY = luaL_optinteger(L, 5, 0);
int width = luaL_optinteger(L, 6, 400);
int height = luaL_optinteger(L, 7, 240);
int xI = fmax(floor((double)offsetX / map->tileSizeX), 0); // initial tile X
int xF = fmin(ceil((double)(offsetX + width) / map->tileSizeX), map->width); // final tile X
int yI = fmax(floor((double)offsetY / map->tileSizeY), 0); // initial tile Y
int yF = fmin(ceil((double)(offsetY + height) / map->tileSizeY), map->height); // final tile Y
if (sf2d_get_current_screen() == GFX_TOP)
sf2d_set_scissor_test(GPU_SCISSOR_NORMAL, x, y, fmin(width, 400 - x), fmin(height, 240 - y)); // Scissor test doesn't work when x/y + width > screenWidth/Height
else
sf2d_set_scissor_test(GPU_SCISSOR_NORMAL, x, y, fmin(width, 320 - x), fmin(height, 240 - y));
int texX = 0;
int texY = 0;
if (map->texture->blendColor == 0xffffffff) {
for (int xp=0; xp<map->width; xp++) {
for (int yp=0; yp<map->height; yp++) {
for (int xp = xI; xp < xF; xp++) {
for (int yp = yI; yp < yF; yp++) {
u16 tile = getTile(map, xp, yp);
getTilePos(map, tile, &texX, &texY);
sf2d_draw_texture_part(map->texture->texture, (x+(map->tileSizeX*xp)+(xp*map->spaceX)), (y+(map->tileSizeY*yp)+(yp*map->spaceY)), texX, texY, map->tileSizeX, map->tileSizeY);
sf2d_draw_texture_part(map->texture->texture, x+(map->tileSizeX*xp)+(xp*map->spaceX)-offsetX, y+(map->tileSizeY*yp)+(yp*map->spaceY)-offsetY, texX, texY, map->tileSizeX, map->tileSizeY);
}
}
} else {
for (int xp=0; xp<map->width; xp++) {
for (int yp=0; yp<map->height; yp++) {
for (int xp = xI; xp < xF; xp++) {
for (int yp = yI; yp < yF; yp++) {
u16 tile = getTile(map, xp, yp);
getTilePos(map, tile, &texX, &texY);
sf2d_draw_texture_part_blend(map->texture->texture, (x+(map->tileSizeX*xp)+(xp*map->spaceX)), (y+(map->tileSizeY*yp)+(yp*map->spaceY)), texX, texY, map->tileSizeX, map->tileSizeY, map->texture->blendColor);
sf2d_draw_texture_part_blend(map->texture->texture, x+(map->tileSizeX*xp)+(xp*map->spaceX)-offsetX, y+(map->tileSizeY*yp)+(yp*map->spaceY)-offsetY, texX, texY, map->tileSizeX, map->tileSizeY, map->texture->blendColor);
}
}
}
sf2d_set_scissor_test(lua_scissor.mode, lua_scissor.x, lua_scissor.y, lua_scissor.width, lua_scissor.height);
return 0;
}
/***
Unload a map.
@function :unload
@within Methods
*/
static int map_unload(lua_State *L) {
map_userdata *map = luaL_checkudata(L, 1, "LMap");
free(map->data);
// Remove the reference to the texture in the registry
// registry[map_userdata] = nil
lua_pushnil(L);
lua_copy(L, 1, -1); // map_userdata
lua_pushnil(L);
lua_settable(L, LUA_REGISTRYINDEX);
return 0;
}
@ -208,7 +252,6 @@ Return the size of a map.
@function :getSize
@treturn number width of the map, in tiles
@treturn number height of the map, in tiles
@within Methods
*/
static int map_getSize(lua_State *L) {
map_userdata *map = luaL_checkudata(L, 1, "LMap");
@ -225,7 +268,6 @@ Return the value of a tile.
@tparam number x X position of the tile (in tiles)
@tparam number y Y position of the tile (in tiles)
@treturn number value of the tile
@within Methods
*/
static int map_getTile(lua_State *L) {
map_userdata *map = luaL_checkudata(L, 1, "LMap");
@ -243,7 +285,6 @@ Set the value of a tile.
@tparam number x X position of the tile (in tiles)
@tparam number y Y position of the tile (in tiles)
@tparam number value new value for the tile
@within Methods
*/
static int map_setTile(lua_State *L) {
map_userdata *map = luaL_checkudata(L, 1, "LMap");

View file

@ -20,20 +20,23 @@ u32 bufferSize = 0;
Initialize the mic module.
@function init
@tparam[opt=0x50000] number bufferSize size of the buffer (must be a multiple of 0x1000)
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer/string error code/message
*/
static int mic_init(lua_State *L) {
bufferSize = luaL_optinteger(L, 1, 0x50000);
buff = memalign(0x1000, bufferSize);
if (buff == NULL) {
lua_pushnil(L);
lua_pushboolean(L, false);
lua_pushstring(L, "Couldn't allocate buffer");
return 2;
}
Result ret = micInit(buff, bufferSize);
if (ret) {
free(buff);
lua_pushnil(L);
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}

View file

@ -13,13 +13,17 @@ The `news` module.
#include <stdlib.h>
#include <string.h>
bool initStateNews = false;
/***
Initialize the news module.
@function init
*/
static int news_init(lua_State *L) {
newsInit();
if (!initStateNews) {
newsInit();
initStateNews = true;
}
return 0;
}
@ -63,8 +67,10 @@ Disable the news module.
@function shutdown
*/
static int news_shutdown(lua_State *L) {
newsExit();
if (initStateNews) {
newsExit();
initStateNews = false;
}
return 0;
}
@ -83,3 +89,10 @@ int luaopen_news_lib(lua_State *L) {
void load_news_lib(lua_State *L) {
luaL_requiref(L, "ctr.news", luaopen_news_lib, 0);
}
void unload_news_lib(lua_State *L) {
if (initStateNews) {
newsExit();
initStateNews = false;
}
}

View file

@ -10,13 +10,19 @@ The `ptm` module.
#include <lua.h>
#include <lauxlib.h>
bool initStatePTM = false;
/***
Initialize the PTM module.
@function init
*/
static int ptm_init(lua_State *L) {
ptmuInit();
ptmSysmInit();
if (!initStatePTM) {
ptmuInit();
ptmSysmInit();
initStatePTM = true;
}
return 0;
}
@ -26,8 +32,12 @@ Disable the PTM module.
@function shutdown
*/
static int ptm_shutdown(lua_State *L) {
ptmuExit();
ptmSysmExit();
if (initStatePTM) {
ptmuExit();
ptmSysmExit();
initStatePTM = false;
}
return 0;
}
@ -35,13 +45,13 @@ static int ptm_shutdown(lua_State *L) {
/***
Return the shell state.
@function getShellState
@treturn number shell state
@treturn boolean shell state, `true` if open, `false` if closed.
*/
static int ptm_getShellState(lua_State *L) {
u8 out = 0;
PTMU_GetShellState(&out);
lua_pushinteger(L, out);
lua_pushboolean(L, out);
return 1;
}
@ -107,7 +117,9 @@ Setup the new 3DS CPU features (overclock, 4 cores ...)
@newonly
@function configureNew3DSCPU
@tparam boolean enable enable the New3DS CPU features
@treturn boolean `true` if everything went fine
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int ptm_configureNew3DSCPU(lua_State *L) {
u8 conf = false;
@ -146,3 +158,10 @@ int luaopen_ptm_lib(lua_State *L) {
void load_ptm_lib(lua_State *L) {
luaL_requiref(L, "ctr.ptm", luaopen_ptm_lib, 0);
}
void unload_ptm_lib(lua_State *L) {
if (initStatePTM) {
ptmuExit();
ptmSysmExit();
}
}

View file

@ -22,6 +22,9 @@ static const struct luaL_Reg qtm_methods[];
/***
Initialize the QTM module.
@function init
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] integer error code
*/
static int qtm_init(lua_State *L) {
Result ret = qtmInit();
@ -59,7 +62,7 @@ static int qtm_checkInitialized(lua_State *L) {
/***
Return informations about the headtracking
@function getHeadTrackingInfo
@function getHeadtrackingInfo
@treturn qtmInfos QTM informations
*/
static int qtm_getHeadtrackingInfo(lua_State *L) {
@ -121,8 +124,10 @@ Convert QTM coordinates to screen coordinates
@tparam number coordinates index
@tparam[opt=400] number screenWidth specify a screen width
@tparam[opt=320] number screenHeight specify a screen height
@treturn number screen X coordinate
@treturn number screen Y coordinate
@treturn[1] number screen X coordinate
@treturn[1] number screen Y coordinate
@treturn[2] nil in case of error
@treturn[2] string error message
*/
static int qtm_convertCoordToScreen(lua_State *L) {
qtm_userdata *info = luaL_checkudata(L, 1, "LQTM");
@ -130,7 +135,7 @@ static int qtm_convertCoordToScreen(lua_State *L) {
index = index - 1; // Lua index begins at 1
if (index > 3 || index < 0) {
lua_pushnil(L);
lua_pushnil(L);
lua_pushstring(L, "Index must be between 1 and 3");
return 2;
}
float screenWidth = luaL_optnumber(L, 3, 400.0f);

View file

@ -1,6 +1,7 @@
/***
The `socket` module. Almost like luasocket, but for the TCP part only.
The UDP part is only without connection.
All sockets are not blocking by default.
@module ctr.socket
@usage local socket = require("ctr.socket")
*/
@ -8,6 +9,7 @@ The UDP part is only without connection.
#include <3ds.h>
#include <3ds/types.h>
#include <3ds/services/soc.h>
#include <3ds/services/sslc.h>
#include <lapi.h>
#include <lauxlib.h>
@ -15,7 +17,9 @@ The UDP part is only without connection.
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
@ -26,21 +30,61 @@ typedef struct {
int socket;
struct sockaddr_in addr;
struct hostent *host; // only used for client sockets
sslcContext sslContext;
bool isSSL;
} socket_userdata;
bool initStateSocket = false;
u32 rootCertChain = 0;
/***
Initialize the socket module
@function init
@tparam[opt=0x100000] number buffer size (in bytes), must be a multiple of 0x1000
@treturn[1] boolean `true` if everything went fine
@treturn[2] boolean `false` in case of error
@treturn[2] number/string error code/message
*/
static int socket_init(lua_State *L) {
u32 size = luaL_optinteger(L, 1, 0x100000);
Result ret = socInit((u32*)memalign(0x1000, size), size);
if (!initStateSocket) {
u32 size = luaL_optinteger(L, 1, 0x100000);
if (size%0x1000 != 0) {
lua_pushboolean(L, false);
lua_pushstring(L, "Not a multiple of 0x1000");
return 2;
}
u32* mem = (u32*)memalign(0x1000, size);
if (mem == NULL) {
lua_pushboolean(L, false);
lua_pushstring(L, "Failed to allocate memory");
return 2;
}
Result ret = socInit(mem, size);
if (ret) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
if (R_FAILED(ret)) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
ret = sslcInit(0);
if (R_FAILED(ret)) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
sslcCreateRootCertChain(&rootCertChain);
sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_CyberTrust, NULL);
sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_AddTrust_External_CA, NULL);
sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_COMODO, NULL);
sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_USERTrust, NULL);
sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_DigiCert_EV, NULL);
initStateSocket = true;
}
lua_pushboolean(L, true);
@ -48,11 +92,16 @@ static int socket_init(lua_State *L) {
}
/***
Disable the socket module. Must be called before exiting ctrµLua.
Disable the socket module.
@function shutdown
*/
static int socket_shutdown(lua_State *L) {
socExit();
if (initStateSocket) {
sslcDestroyRootCertChain(rootCertChain);
sslcExit();
socExit();
initStateSocket = false;
}
return 0;
}
@ -60,7 +109,9 @@ static int socket_shutdown(lua_State *L) {
/***
Return a TCP socket.
@function tcp
@treturn TCPMaster TCP socket
@treturn[1] TCPMaster TCP socket
@treturn[2] nil in case of error
@treturn[2] string error message
*/
static int socket_tcp(lua_State *L) {
socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata));
@ -76,13 +127,18 @@ static int socket_tcp(lua_State *L) {
userdata->addr.sin_family = AF_INET;
userdata->isSSL = false;
fcntl(userdata->socket, F_SETFL, fcntl(userdata->socket, F_GETFL, 0)|O_NONBLOCK);
return 1;
}
/***
Return an UDP socket.
@function udp
@treturn UDPMaster UDP socket
@treturn[1] UDPMaster UDP socket
@treturn[2] nil in case of error
@treturn[2] string error message
*/
static int socket_udp(lua_State *L) {
socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata));
@ -92,15 +148,39 @@ static int socket_udp(lua_State *L) {
userdata->socket = socket(AF_INET, SOCK_DGRAM, 0);
if (userdata->socket < 0) {
lua_pushnil(L);
lua_pushstring(L, "Failed to create a TCP socket");
lua_pushstring(L, strerror(errno));
return 2;
}
userdata->addr.sin_family = AF_INET;
fcntl(userdata->socket, F_SETFL, fcntl(userdata->socket, F_GETFL, 0)|O_NONBLOCK);
return 1;
}
/***
Add a trusted root CA to the certChain.
@function addTrustedRootCA
@tparam string cert DER cert
@treturn[1] boolean `true` if everything went fine
@treturn[2] nil in case of error
@treturn[2] number error code
*/
static int socket_addTrustedRootCA(lua_State *L) {
size_t size = 0;
const char* cert = luaL_checklstring(L, 1, &size);
Result ret = sslcAddTrustedRootCA(rootCertChain, (u8*)cert, size, NULL);
if (R_FAILED(ret)) {
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
lua_pushboolean(L, true);
return 1;
}
/***
All sockets
@section sockets
@ -115,7 +195,7 @@ static int socket_bind(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
int port = luaL_checkinteger(L, 2);
userdata->addr.sin_addr.s_addr = htonl(INADDR_ANY);
userdata->addr.sin_addr.s_addr = gethostid();
userdata->addr.sin_port = htons(port);
bind(userdata->socket, (struct sockaddr*)&userdata->addr, sizeof(userdata->addr));
@ -130,11 +210,73 @@ Close an existing socket.
static int socket_close(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
if (userdata->isSSL) {
sslcDestroyContext(&userdata->sslContext);
}
closesocket(userdata->socket);
return 0;
}
/***
Get some informations from a socket.
@function :getpeername
@treturn string IP
@treturn number port
*/
static int socket_getpeername(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
struct sockaddr_in addr;
socklen_t addrSize = sizeof(addr);
getpeername(userdata->socket, (struct sockaddr*)&addr, &addrSize);
lua_pushstring(L, inet_ntoa(addr.sin_addr));
lua_pushinteger(L, ntohs(addr.sin_port));
return 2;
}
/***
Get some local informations from a socket.
@function :getsockname
@treturn string IP
@treturn number port
*/
static int socket_getsockname(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
struct sockaddr_in addr;
socklen_t addrSize = sizeof(addr);
getsockname(userdata->socket, (struct sockaddr*)&addr, &addrSize);
lua_pushstring(L, inet_ntoa(addr.sin_addr));
lua_pushinteger(L, ntohs(addr.sin_port));
return 2;
}
/***
Set if the socket should be blocking.
@function :setBlocking
@tparam[opt=true] boolean block if `false`, the socket won't block
*/
static int socket_setBlocking(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
bool block = true;
if (lua_isboolean(L, 2))
block = lua_toboolean(L, 2);
int flags = fcntl(userdata->socket, F_GETFL, 0);
flags = block?(flags&~O_NONBLOCK):(flags|O_NONBLOCK);
fcntl(userdata->socket, F_SETFL, flags);
return 0;
}
/***
TCP Sockets
@section TCP
@ -151,6 +293,7 @@ static int socket_accept(lua_State *L) {
socket_userdata *client = lua_newuserdata(L, sizeof(*client));
luaL_getmetatable(L, "LSocket");
lua_setmetatable(L, -2);
client->isSSL = false;
socklen_t addrSize = sizeof(client->addr);
client->socket = accept(userdata->socket, (struct sockaddr*)&client->addr, &addrSize);
@ -167,6 +310,7 @@ Connect a socket to a server. The TCP object becomes a TCPClient object.
@function :connect
@tparam string host address of the host
@tparam number port port of the server
@tparam[opt=false] boolean ssl use SSL if `true`
@treturn[1] boolean true if success
@treturn[2] boolean false if failed
@treturn[2] string error string
@ -175,11 +319,12 @@ static int socket_connect(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
char *addr = (char*)luaL_checkstring(L, 2);
int port = luaL_checkinteger(L, 3);
bool ssl = lua_toboolean(L, 4);
userdata->host = gethostbyname(addr);
if (userdata->host == NULL) {
lua_pushnil(L);
lua_pushstring(L, "No such host");
lua_pushstring(L, strerror(errno));
return 2;
}
@ -188,10 +333,22 @@ static int socket_connect(lua_State *L) {
if (connect(userdata->socket, (const struct sockaddr*)&userdata->addr, sizeof(userdata->addr)) < 0) {
lua_pushnil(L);
lua_pushstring(L, "Connection failed");
lua_pushstring(L, strerror(errno));
return 2;
}
if (ssl) { // SSL context setup
sslcCreateContext(&userdata->sslContext, userdata->socket, SSLCOPT_Default, addr);
sslcContextSetRootCertChain(&userdata->sslContext, rootCertChain);
if (R_FAILED(sslcStartConnection(&userdata->sslContext, NULL, NULL))) {
sslcDestroyContext(&userdata->sslContext);
lua_pushnil(L);
lua_pushstring(L, "SSL connection failed");
return 2;
}
userdata->isSSL = true;
}
lua_pushboolean(L, 1);
return 1;
}
@ -218,7 +375,9 @@ If no data is avaible, it returns an empty string (non-blocking).
"a" to receive everything,
"l" to receive the next line, skipping the end of line,
"L" to receive the next line, keeping the end of line.
@treturn string data
@treturn[1] string data
@treturn[2] nil in case of error
@treturn[2] integer error code
*/
static int socket_receive(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
@ -237,7 +396,11 @@ static int socket_receive(lua_State *L) {
luaL_buffinit(L, &b);
char buff;
while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff);
if (!userdata->isSSL) {
while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff);
} else {
while (!R_FAILED(sslcRead(&userdata->sslContext, &buff, 1, false)) && buff != '\n') luaL_addchar(&b, buff);
}
luaL_pushresult(&b);
return 1;
@ -247,7 +410,11 @@ static int socket_receive(lua_State *L) {
luaL_buffinit(L, &b);
char buff;
while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff);
if (!userdata->isSSL) {
while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff);
} else {
while (buff != '\n' && !R_FAILED(sslcRead(&userdata->sslContext, &buff, 1, false))) luaL_addchar(&b, buff);
}
luaL_pushresult(&b);
return 1;
@ -258,7 +425,17 @@ static int socket_receive(lua_State *L) {
}
char *buff = malloc(count+1);
int len = recv(userdata->socket, buff, count, flags);
int len;
if (!userdata->isSSL) {
len = recv(userdata->socket, buff, count, flags);
} else {
len = sslcRead(&userdata->sslContext, buff, count, false);
if (R_FAILED(len)) {
lua_pushnil(L);
lua_pushinteger(L, len);
return 2;
}
}
*(buff+len) = 0x0; // text end
lua_pushstring(L, buff);
@ -269,14 +446,32 @@ static int socket_receive(lua_State *L) {
Send some data over the TCP socket.
@function :send
@tparam string data data to send
@treturn number amount of data sent
@treturn[1] number amount of data sent
@treturn[2] nil in case of error
@treturn[2] integer/string error code/message
*/
static int socket_send(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
size_t size = 0;
char *data = (char*)luaL_checklstring(L, 2, &size);
size_t sent = send(userdata->socket, data, size, 0);
ssize_t sent;
if (!userdata->isSSL) {
sent = send(userdata->socket, data, size, 0);
} else {
sent = sslcWrite(&userdata->sslContext, data, size);
if (R_FAILED(sent)) {
lua_pushnil(L);
lua_pushinteger(L, sent);
return 2;
}
}
if (sent < 0) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}
lua_pushinteger(L, sent);
return 1;
@ -288,97 +483,114 @@ UDP sockets
*/
/***
Receive some data from a server.
Receive a datagram from the UDP object.
@function :receivefrom
@tparam number count amount of data to receive
@tparam string host host name
@tparam number port port
@treturn string data
@tparam[opt=8191] number count maximum amount of bytes to receive from the datagram. Must be lower than 8192.
@treturn[1] string data
@treturn[1] string IP address of the sender
@treturn[1] integer port number of the sender
@treturn[2] nil in case of error or no datagram to receive
@treturn[2] string error message
*/
static int socket_receivefrom(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
int count = luaL_checkinteger(L, 2);
size_t namesize = 0;
char *hostname = (char*)luaL_optlstring(L, 3, NULL, &namesize);
int port = luaL_optinteger(L, 4, 0);
struct sockaddr_in from = {0};
if (hostname != NULL) { // For a server
struct hostent *hostinfo = gethostbyname(hostname);
if (hostinfo == NULL) {
lua_pushnil(L);
return 1;
}
from.sin_addr = *(struct in_addr*)hostinfo->h_addr;
from.sin_port = htons(port);
from.sin_family = AF_INET;
int count = luaL_optinteger(L, 2, 8191);
struct sockaddr_in from;
socklen_t addr_len;
char* buffer = calloc(1, count+1);
ssize_t n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr *)&from, &addr_len);
if (n == 0) {
free(buffer);
lua_pushnil(L);
lua_pushstring(L, "nothing to receive");
return 2;
} else if (n < 0) {
free(buffer);
lua_pushnil(L);
lua_pushstring(L, strerror(n));
return 2;
}
char* buffer = malloc(count+1);
int n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr*)&from, NULL);
*(buffer+n) = 0x0;
lua_pushstring(L, buffer);
if (hostname != NULL) {
return 1;
} else {
lua_pushstring(L, inet_ntoa(from.sin_addr));
lua_pushinteger(L, ntohs(from.sin_port));
return 3;
}
lua_pushstring(L, inet_ntoa(from.sin_addr));
lua_pushinteger(L, ntohs(from.sin_port));
free(buffer);
return 3;
}
/***
Send some data to a server.
Send a datagram to the specified IP and port.
@function :sendto
@tparam string data data to send
@tparam string host host name
@tparam number port port
@tparam string host IP/hostname of the recipient
@tparam number port port number of the recipient
@treturn[1] boolean true in case of success
@treturn[2] boolean false in case of error
@treturn[2] string error message
*/
static int socket_sendto(lua_State *L) {
socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket");
size_t datasize = 0;
char *data = (char*)luaL_checklstring(L, 2, &datasize);
size_t namesize = 0;
char *hostname = (char*)luaL_checklstring(L, 3, &namesize);
size_t datasize;
const char *data = luaL_checklstring(L, 2, &datasize);
const char *hostname = luaL_checkstring(L, 3);
int port = luaL_checkinteger(L, 4);
struct hostent *hostinfo = gethostbyname(hostname);
if (hostinfo == NULL) {
lua_pushnil(L);
return 1;
lua_pushboolean(L, false);
lua_pushstring(L, "unknown host");
return 2;
}
struct sockaddr_in to = {0};
to.sin_addr = *(struct in_addr*)hostinfo->h_addr;
struct sockaddr_in to;
to.sin_addr = *(struct in_addr *)hostinfo->h_addr;
to.sin_port = htons(port);
to.sin_family = AF_INET;
sendto(userdata->socket, data, datasize, 0, (struct sockaddr*)&to, sizeof(to));
return 0;
ssize_t n = sendto(userdata->socket, data, datasize, 0, (struct sockaddr *)&to, sizeof(to));
if (n < 0) {
lua_pushboolean(L, false);
lua_pushstring(L, strerror(n));
return 2;
}
lua_pushboolean(L, true);
return 1;
}
// module functions
// Module functions
static const struct luaL_Reg socket_functions[] = {
{"init", socket_init },
{"shutdown", socket_shutdown},
{"tcp", socket_tcp },
{"udp", socket_udp },
{ "init", socket_init },
{ "shutdown", socket_shutdown },
{ "tcp", socket_tcp },
{ "udp", socket_udp },
{ "addTrustedRootCA", socket_addTrustedRootCA },
{NULL, NULL}
};
// object
// Object methods
static const struct luaL_Reg socket_methods[] = {
{"accept", socket_accept },
{"bind", socket_bind },
{"close", socket_close },
{"__gc", socket_close },
{"connect", socket_connect },
{"listen", socket_listen },
{"receive", socket_receive },
{"receivefrom", socket_receivefrom},
{"send", socket_send },
{"sendto", socket_sendto },
{ "accept", socket_accept },
{ "bind", socket_bind },
{ "close", socket_close },
{ "setBlocking", socket_setBlocking },
{ "__gc", socket_close },
{ "connect", socket_connect },
{ "listen", socket_listen },
{ "receive", socket_receive },
{ "receivefrom", socket_receivefrom },
{ "send", socket_send },
{ "sendto", socket_sendto },
{ "getpeername", socket_getpeername },
{ "getsockname", socket_getsockname },
{NULL, NULL}
};

View file

@ -12,6 +12,12 @@ The `gfx.texture` module.
#include <stdlib.h>
#include <string.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include <png.h>
#include "texture.h"
int getType(const char *name) {
@ -32,12 +38,14 @@ int getType(const char *name) {
// module functions
/***
Load a texture from a file. Supported formats: PNG, JPEG, BMP.
Load a texture from a file. Supported formats: PNG, JPEG, BMP, GIF, PSD, TGA, HDR, PIC, PNM.
@function load
@tparam string path path to the image file
@tparam[opt=PLACE_RAM] number place where to put the loaded texture
@tparam[opt=auto] number type type of the image
@treturn texture the loaded texture object
@tparam[opt=auto] number type type of the image. This is only used to force loading PNG, JPEG or BMP files with the sfil library; any value between 5 and 250 will force using the stbi library. Leave nil to autodetect the format.
@treturn[1] texture the loaded texture object
@treturn[2] nil in case of error
@treturn[2] string error message
*/
static int texture_load(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
@ -46,10 +54,10 @@ static int texture_load(lua_State *L) {
texture_userdata *texture;
texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture));
luaL_getmetatable(L, "LTexture");
lua_setmetatable(L, -2);
if (type==3) type = getType(path);
if (type==0) { //PNG
texture->texture = sfil_load_PNG_file(path, place);
@ -58,21 +66,27 @@ static int texture_load(lua_State *L) {
} else if (type==2) { //BMP
texture->texture = sfil_load_BMP_file(path, place); //appears to be broken right now.
} else {
lua_pushnil(L);
lua_pushstring(L, "Bad type");
return 2;
int w, h;
char* data = (char*)stbi_load(path, &w, &h, NULL, 4);
if (data == NULL) {
lua_pushnil(L);
lua_pushstring(L, "Can't open file");
return 2;
}
texture->texture = sf2d_create_texture_mem_RGBA8(data, w, h, TEXFMT_RGBA8, place);
free(data);
}
if (texture->texture == NULL) {
lua_pushnil(L);
lua_pushstring(L, "No such file");
return 2;
}
texture->scaleX = 1.0f;
texture->scaleY = 1.0f;
texture->blendColor = 0xffffffff;
return 1;
}
@ -88,20 +102,20 @@ static int texture_new(lua_State *L) {
int w = luaL_checkinteger(L, 1);
int h = luaL_checkinteger(L, 2);
u8 place = luaL_checkinteger(L, 3);
texture_userdata *texture;
texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture));
luaL_getmetatable(L, "LTexture");
lua_setmetatable(L, -2);
texture->texture = sf2d_create_texture(w, h, TEXFMT_RGBA8, place);
sf2d_texture_tile32(texture->texture);
texture->scaleX = 1.0f;
texture->scaleY = 1.0f;
texture->blendColor = 0xffffffff;
return 1;
}
@ -113,35 +127,41 @@ Texture object
/***
Draw a texture.
@function :draw
@tparam number x X position
@tparam number y Y position
@tparam[opt=0.0] number rad rotation of the texture (in radians)
@tparam integer x X position
@tparam integer y Y position
@tparam[opt=0.0] number rad rotation of the texture around the hotspot (in radians)
@tparam[opt=0.0] number hotspotX the hostpot X coordinate
@tparam[opt=0.0] number hotspotY the hostpot Y coordinate
*/
static int texture_draw(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
int x = luaL_checkinteger(L, 2);
int y = luaL_checkinteger(L, 3);
float rad = luaL_optnumber(L, 4, 0.0f);
float hotspotX = luaL_optnumber(L, 5, 0.0f);
float hotspotY = luaL_optnumber(L, 6, 0.0f);
if (rad == 0.0f && texture->scaleX == 1.0f && texture->scaleY == 1.0f && texture->blendColor == 0xffffffff) {
sf2d_draw_texture(texture->texture, x, y);
sf2d_draw_texture(texture->texture, x - hotspotX, y - hotspotY);
} else {
sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, 0, 0, texture->texture->width, texture->texture->height, texture->scaleX, texture->scaleY, texture->blendColor);
sf2d_draw_texture_rotate_scale_hotspot_blend(texture->texture, x, y, rad, texture->scaleX, texture->scaleY, hotspotX, hotspotY, texture->blendColor);
}
return 0;
}
/***
Draw a part of the texture
@function :drawPart
@tparam number x X position
@tparam number y Y position
@tparam number sx X position of the beginning of the part
@tparam number sy Y position of the beginning of the part
@tparam number w width of the part
@tparam number h height of the part
@tparam[opt=0.0] number rad rotation of the part (in radians)
@tparam integer x X position
@tparam integer y Y position
@tparam integer sx X position of the beginning of the part
@tparam integer sy Y position of the beginning of the part
@tparam integer w width of the part
@tparam integer h height of the part
@tparam[opt=0.0] number rad rotation of the part around the hotspot (in radians)
@tparam[opt=0.0] number hotspotX the hostpot X coordinate
@tparam[opt=0.0] number hotspotY the hostpot Y coordinate
*/
static int texture_drawPart(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
@ -152,9 +172,11 @@ static int texture_drawPart(lua_State *L) {
int w = luaL_checkinteger(L, 6);
int h = luaL_checkinteger(L, 7);
int rad = luaL_optnumber(L, 8, 0.0f);
sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, texture->blendColor);
float hotspotX = luaL_optnumber(L, 9, 0.0f);
float hotspotY = luaL_optnumber(L, 10, 0.0f);
sf2d_draw_texture_part_rotate_scale_hotspot_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, hotspotX, hotspotY, texture->blendColor);
return 0;
}
@ -179,12 +201,12 @@ Unload a texture.
*/
static int texture_unload(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
if (texture->texture == NULL) return 0;
sf2d_free_texture(texture->texture);
texture->texture = NULL;
return 0;
}
@ -192,16 +214,16 @@ static int texture_unload(lua_State *L) {
Rescale the texture. The default scale is `1.0`.
@function :scale
@tparam number scaleX new scale of the width
@tparam number scaleY new scale of the height
@tparam[opt=scaleX] number scaleY new scale of the height
*/
static int texture_scale(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
float sx = luaL_checknumber(L, 2);
float sy = luaL_checknumber(L, 3);
float sy = luaL_optnumber(L, 3, sx);
texture->scaleX = sx;
texture->scaleY = sy;
return 0;
}
@ -216,9 +238,9 @@ static int texture_getPixel(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
int x = luaL_checkinteger(L, 2);
int y = luaL_checkinteger(L, 3);
lua_pushinteger(L, sf2d_get_pixel(texture->texture, x, y));
return 1;
}
@ -234,9 +256,9 @@ static int texture_setPixel(lua_State *L) {
int x = luaL_checkinteger(L, 2);
int y = luaL_checkinteger(L, 3);
u32 color = luaL_checkinteger(L, 4);
sf2d_set_pixel(texture->texture, x, y, color);
return 0;
}
@ -246,14 +268,106 @@ Set the blend color of the texture.
@tparam number color new blend color
*/
static int texture_setBlendColor(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
u32 color = luaL_checkinteger(L, 2);
texture->blendColor = color;
return 0;
}
/***
Get the blend color of the texture.
@function :getBlendColor
@treturn number the blend color
*/
static int texture_getBlendColor(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
lua_pushinteger(L, texture->blendColor);
return 1;
}
/***
Save a texture to a file.
@function :save
@tparam string filename path to the file to save the texture to
@tparam[opt=TYPE_PNG] number type type of the image to save. Can be TYPE_PNG or TYPE_BMP
@treturn[1] boolean true on success
@treturn[2] boolean `false` in case of error
@treturn[2] string error message
*/
static int texture_save(lua_State *L) {
texture_userdata *texture = luaL_checkudata(L, 1, "LTexture");
const char* path = luaL_checkstring(L, 2);
u8 type = luaL_optinteger(L, 3, 0);
int result = 0;
if (type == 0) { // PNG
FILE* file = fopen(path, "wb");
if (file == NULL) {
lua_pushboolean(L, false);
lua_pushstring(L, "Can open file");
return 2;
}
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop infos = png_create_info_struct(png);
setjmp(png_jmpbuf(png));
png_init_io(png, file);
png_set_IHDR(png, infos, texture->texture->width, texture->texture->height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, infos);
png_bytep row = malloc(4 * texture->texture->width * sizeof(png_byte));
for(int y=0;y<texture->texture->height;y++) {
for (int x=0;x<texture->texture->width;x++) {
((u32*)row)[x] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y));
}
png_write_row(png, row);
}
png_write_end(png, NULL);
fclose(file);
png_free_data(png, infos, PNG_FREE_ALL, -1);
png_destroy_write_struct(&png, &infos);
free(row);
result = 1;
} else if (type == 2) { // BMP
u32* buff = malloc(texture->texture->width * texture->texture->height * 4);
if (buff == NULL) {
lua_pushboolean(L, false);
lua_pushstring(L, "Failed to allocate buffer");
return 2;
}
for (int y=0;y<texture->texture->height;y++) {
for (int x=0;x<texture->texture->width;x++) {
buff[x+(y*texture->texture->width)] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y));
}
}
result = stbi_write_bmp(path, texture->texture->width, texture->texture->height, 4, buff);
free(buff);
} else {
lua_pushboolean(L, false);
lua_pushstring(L, "Not a valid type");
return 2;
}
if (result == 0) {
lua_pushboolean(L, false);
lua_pushstring(L, "Failed to save the texture");
return 2;
}
lua_pushboolean(L, true);
return 1;
}
// object
static const struct luaL_Reg texture_methods[] = {
{ "draw", texture_draw },
@ -264,6 +378,8 @@ static const struct luaL_Reg texture_methods[] = {
{ "getPixel", texture_getPixel },
{ "setPixel", texture_setPixel },
{ "setBlendColor", texture_setBlendColor },
{ "getBlendColor", texture_getBlendColor },
{ "save", texture_save },
{ "__gc", texture_unload },
{NULL, NULL}
};

589
source/uds.c Normal file
View file

@ -0,0 +1,589 @@
/***
The `uds` module. Used for 3DS-to-3DS wireless communication.
The default wlancommID is 0x637472c2.
@module ctr.uds
@usage local uds = require("ctr.uds")
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <3ds/result.h>
#include <3ds/types.h>
#include <3ds/services/uds.h>
#include <lualib.h>
#include <lauxlib.h>
bool initStateUDS = false;
udsBindContext bind = {0};
udsNetworkStruct network = {0};
u8 data_channel = 1;
/***
Initialize the UDS module.
@function init
@tparam[opt=0x3000] number context size in bytes, must be a multiple of 0x1000
@tparam[opt=3DS username] string username UTF-8 username on the network
@treturn[1] boolean `true` on success
@treturn[2] boolean `false` on error
@treturn[2] number error code
*/
static int uds_init(lua_State *L) {
if (!initStateUDS) {
size_t memSize = luaL_optinteger(L, 1, 0x3000);
const char* username = luaL_optstring(L, 2, NULL);
Result ret = udsInit(memSize, username);
if (R_FAILED(ret)) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
initStateUDS = true;
}
lua_pushboolean(L, true);
return 1;
}
/***
Disable the UDS module.
@function shutdown
*/
static int uds_shutdown(lua_State *L) {
udsExit();
initStateUDS = false;
return 0;
}
/***
Scan for network beacons.
@function scan
@tparam[opt=0x637472c2] number commID application local-WLAN unique ID
@tparam[opt=0] number id8 additional ID to use different network types
@tparam[opt=all] string hostMAC if set, only scan networks from this MAC address (format: `XX:XX:XX:XX:XX:XX`, with hexadecimal values)
@treturn[1] table a table containing beacons objects
@treturn[2] nil
@treturn[2] number/string error code
*/
static int uds_scan(lua_State *L) {
static const size_t tmpbuffSize = 0x4000;
u32* tmpbuff = malloc(tmpbuffSize);
udsNetworkScanInfo* networks = NULL;
size_t totalNetworks = 0;
u32 wlanCommID = luaL_optinteger(L, 1, 0x637472c2);
u8 id8 = luaL_optinteger(L, 2, 0);
// MAC address conversion
const char* hostMACString = luaL_optstring(L, 3, NULL);
u8* hostMAC;
if (hostMACString != NULL) {
hostMAC = malloc(6*sizeof(u8));
unsigned int tmpMAC[6];
if (sscanf(hostMACString, "%x:%x:%x:%x:%x:%x", &tmpMAC[0], &tmpMAC[1], &tmpMAC[2], &tmpMAC[3], &tmpMAC[4], &tmpMAC[5]) != 6) {
free(tmpbuff);
free(hostMAC);
lua_pushnil(L);
lua_pushstring(L, "Bad MAC formating");
return 2;
}
for (int i=0;i<6;i++) {
hostMAC[i] = tmpMAC[i];
}
} else {
hostMAC = NULL;
}
udsConnectionStatus status;
udsGetConnectionStatus(&status);
Result ret = udsScanBeacons(tmpbuff, tmpbuffSize, &networks, &totalNetworks, wlanCommID, id8, hostMAC, (status.status!=0x03));
free(tmpbuff);
free(hostMAC);
if (R_FAILED(ret)) {
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
// Convert the networks to a table of userdatas
lua_createtable(L, 0, totalNetworks);
for (int i=1;i<=totalNetworks;i++) {
udsNetworkScanInfo* beacon = lua_newuserdata(L, sizeof(udsNetworkScanInfo));
luaL_getmetatable(L, "LUDSBeaconScan");
lua_setmetatable(L, -2);
memcpy(beacon, &networks[0], sizeof(udsNetworkScanInfo));
lua_seti(L, -3, i);
}
free(networks);
return 1;
}
/***
Check for data in the receive buffer.
@function available
@treturn boolean `true` if there is data to receive, `false` if not
*/
static int uds_available(lua_State *L) {
lua_pushboolean(L, udsWaitDataAvailable(&bind, false, false));
return 1;
}
/***
Return a packet from the receive buffer.
@function receive
@tparam[opt=maximum] number size maximum size of the data to receive
@treturn string data, can be an empty string
@treturn number source node, 0 if nothing has been received
*/
static int uds_receive(lua_State *L) {
size_t maxSize = luaL_optinteger(L, 1, UDS_DATAFRAME_MAXSIZE);
char* buff = malloc(maxSize);
if (buff == NULL) luaL_error(L, "Memory allocation error");
size_t received = 0;
u16 sourceID = 0;
Result ret = udsPullPacket(&bind, buff, maxSize, &received, &sourceID);
if (R_FAILED(ret)) {
free(buff);
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
lua_pushlstring(L, buff, received);
free(buff);
lua_pushinteger(L, sourceID);
return 2;
}
/***
Send a packet to a node.
@function send
@tparam string data data to send
@tparam[opt=BROADCAST] number nodeID nodeID to send the packet to
*/
static int uds_send(lua_State *L) {
size_t size = 0;
const char *buff = luaL_checklstring(L, 1, &size);
u16 nodeID = luaL_optinteger(L, 2, UDS_BROADCAST_NETWORKNODEID);
udsSendTo(nodeID, data_channel, 0, buff, size);
return 0;
}
/***
Return information about nodes in the networks
@function getNodesInfo
@treturn tablea table containing nodes informations, as tables (not userdatas).
A node table is like: `{ username = "azerty", nodeID = 12 }`
*/
static int uds_getNodesInfo(lua_State *L) {
lua_newtable(L);
for (int i=0;i<UDS_MAXNODES;i++) {
udsNodeInfo node;
udsGetNodeInformation(i, &node);
if (!udsCheckNodeInfoInitialized(&node)) continue;
lua_createtable(L, 0, 2);
char tmpstr[256];
memset(tmpstr, 0, sizeof(tmpstr));
udsGetNodeInfoUsername(&node, tmpstr);
lua_pushstring(L, tmpstr);
lua_setfield(L, -2, "username");
lua_pushinteger(L, node.NetworkNodeID);
lua_setfield(L, -2, "nodeID");
lua_seti(L, -3, i+1);
}
return 1;
}
/***
Return the application data of the current network.
@function getAppData
@tparam[opt=0x4000] number maxSize maximum application data size to return
@treturn string application data
*/
static int uds_getAppData(lua_State *L) {
size_t maxSize = luaL_optinteger(L, 1, 0x4000);
char* buff = malloc(maxSize);
if (buff == NULL) luaL_error(L, "Memory allocation error");
size_t size = 0;
udsGetApplicationData(buff, maxSize, &size);
lua_pushlstring(L, buff, size);
free(buff);
return 1;
}
/***
Client part
@section client
*/
/***
Connect to a network.
@function connect
@tparam beaconScan beacon beacon to connect to
@tparam[opt=""] string passphrase passphrase for the network
@tparam[opt=CLIENT] number conType type of connection, can be `CONTYPE_CLIENT` or `CONTYPE_SPECTATOR`
@tparam[opt=BROADCAST] number nodeID nodeID to receive data from
@tparam[opt=default] number recvBuffSize size of the buffer that receives data
@tparam[opt=1] number dataChannel data channel of the network; this should be 1
@treturn[1] boolean `true` on success
@treturn[2] boolean `false` on error
@treturn[2] number error code
*/
static int uds_connect(lua_State *L) {
udsNetworkScanInfo* beacon = luaL_checkudata(L, 1, "LUDSBeaconScan");
const char* passphrase = luaL_optstring(L, 2, "");
size_t passphraseSize = strlen(passphrase)+1;
udsConnectionType connType = luaL_optinteger(L, 3, UDSCONTYPE_Client);
u16 recvNetworkNodeID = luaL_optinteger(L, 4, UDS_BROADCAST_NETWORKNODEID);
u32 recvBufferSize = luaL_optinteger(L, 5, UDS_DEFAULT_RECVBUFSIZE);
u8 dataChannel = luaL_optinteger(L, 6, 1);
Result ret = udsConnectNetwork(&beacon->network, passphrase, passphraseSize, &bind, recvNetworkNodeID, connType, dataChannel, recvBufferSize);
if (R_FAILED(ret)) {
lua_pushnil(L);
lua_pushinteger(L, ret);
return 2;
}
lua_pushboolean(L, true);
return 1;
}
/***
Disconnect from the network.
@function disconnect
*/
static int uds_disconnect(lua_State *L) {
udsDisconnectNetwork();
udsUnbind(&bind);
return 0;
}
/***
Server part
@section server
*/
/***
Create a network.
@function createNetwork
@tparam[opt=""] string passphrase passphrase of the network
@tparam[opt=16] number maxNodes maximum number of nodes that can be connected to the network, including the host (max 16)
@tparam[opt=0x637472c2] number commID application local-WLAN unique ID
@tparam[opt=default] number recvBuffSize size of the buffer that receives data
@tparam[opt=1] number dataChannel data channel of the network; this should be 1
@treturn[1] boolean `true` on success
@treturn[2] boolean `false` on error
@treturn[2] number error code
*/
static int uds_createNetwork(lua_State *L) {
size_t passSize = 0;
const char *pass = luaL_optlstring(L, 1, "", &passSize);
u8 maxNodes = luaL_optinteger(L, 2, UDS_MAXNODES);
u32 commID = luaL_optinteger(L, 3, 0x637472c2);
u32 recvBuffSize = luaL_optinteger(L, 4, UDS_DEFAULT_RECVBUFSIZE);
u8 dataChannel = luaL_optinteger(L, 5, 1);
udsGenerateDefaultNetworkStruct(&network, commID, dataChannel, maxNodes);
Result ret = udsCreateNetwork(&network, pass, passSize, &bind, dataChannel, recvBuffSize);
if (R_FAILED(ret)) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/***
Set the application data of the created network.
@function setAppData
@tparam string appData application data
@treturn[1] boolean `true` on success
@treturn[2] boolean `false` on error
@treturn[2] number error code
*/
static int uds_setAppData(lua_State *L) {
size_t size = 0;
const char* data = luaL_checklstring(L, 1, &size);
Result ret = udsSetApplicationData(data, size);
if (R_FAILED(ret)) {
lua_pushboolean(L, false);
lua_pushinteger(L, ret);
return 2;
}
lua_pushboolean(L, true);
return 1;
}
/***
Destroy the network.
@function destroyNetwork
*/
static int uds_destroyNetwork(lua_State *L) {
udsDestroyNetwork();
udsUnbind(&bind);
return 0;
}
/***
Eject all the spectators connected to the network.
@function ejectSpectators
@tparam[opt=false] boolean reallow set to `true` to still allow the spectators to connect
*/
static int uds_ejectSpectators(lua_State *L) {
bool reallow = false;
if (lua_isboolean(L, 1))
reallow = lua_toboolean(L, 1);
udsEjectSpectator();
if (reallow)
udsAllowSpectators();
return 0;
}
/***
Eject a client connected to the network.
@function ejectClient
@tparam[opt=BROADCAST] number nodeID node ID of the client to eject, or `BROADCAST` for all the clients
*/
static int uds_ejectClient(lua_State *L) {
u16 nodeID = luaL_optinteger(L, 1, UDS_BROADCAST_NETWORKNODEID);
udsEjectClient(nodeID);
return 0;
}
/***
beaconScan
@section beaconScan
*/
static const struct luaL_Reg beaconScan_methods[];
static int beaconScan___index(lua_State *L) {
udsNetworkScanInfo* beacon = luaL_checkudata(L, 1, "LUDSBeaconScan");
if (lua_isstring(L, 2)) {
const char* index = luaL_checkstring(L, 2);
/***
@tfield integer channel Wifi channel of the beacon
*/
if (!strcmp(index, "channel")) {
lua_pushinteger(L, beacon->datareply_entry.channel);
return 1;
/***
@tfield string mac MAC address of the beacon (`mac` for lowercase, `MAC` for uppercase)
@usage
beacon.mac -> ab:cd:ef:ab:cd:ef
beacon.MAC -> AB:CD:EF:AB:CD:EF
*/
} else if (!strcmp(index, "mac") && !strcmp(index, "MAC")) {
char* macString = malloc(18);
if (macString == NULL) luaL_error(L, "Out of memory");
if (index[1] == 'm') { // lowercase
sprintf(macString, "%0x2:%0x2:%0x2:%0x2:%0x2:%0x2", beacon->datareply_entry.mac_address[0], beacon->datareply_entry.mac_address[1], beacon->datareply_entry.mac_address[2], beacon->datareply_entry.mac_address[3], beacon->datareply_entry.mac_address[4], beacon->datareply_entry.mac_address[5]);
} else {
sprintf(macString, "%0X2:%0X2:%0X2:%0X2:%0X2:%0X2", beacon->datareply_entry.mac_address[0], beacon->datareply_entry.mac_address[1], beacon->datareply_entry.mac_address[2], beacon->datareply_entry.mac_address[3], beacon->datareply_entry.mac_address[4], beacon->datareply_entry.mac_address[5]);
}
lua_pushstring(L, macString);
free(macString);
return 1;
/***
@tfield table nodes a table containing nodes informations, as tables (not userdatas).
A node table is like: `{ username = "azerty", nodeID = 12 }`
*/
} else if (!strcmp(index, "nodes")) {
lua_newtable(L);
for (int i=0;i<UDS_MAXNODES;i++) {
if (!udsCheckNodeInfoInitialized(&beacon->nodes[i])) continue;
lua_createtable(L, 0, 2);
char tmpstr[256]; // 256 is maybe too much ... But we have a lot of RAM.
memset(tmpstr, 0, sizeof(tmpstr));
udsGetNodeInfoUsername(&beacon->nodes[i], tmpstr);
lua_pushstring(L, tmpstr);
lua_setfield(L, -2, "username");
lua_pushinteger(L, (&beacon->nodes[i])->NetworkNodeID);
lua_setfield(L, -2, "nodeID");
lua_seti(L, -3, i+1);
}
return 1;
/***
@tfield number id8 id8 of the beacon's network
*/
} else if (!strcmp(index, "id8")) {
lua_pushinteger(L, beacon->network.id8);
return 1;
/***
@tfield number networkID random ID of the network
*/
} else if (!strcmp(index, "networkID")) {
lua_pushinteger(L, beacon->network.networkID);
return 1;
/***
@tfield boolean allowSpectators `true` if new spectators are allowed on the network
*/
} else if (!strcmp(index, "allowSpectators")) {
lua_pushboolean(L, !(beacon->network.attributes&UDSNETATTR_DisableConnectSpectators));
return 1;
/***
@tfield boolean allowClients `true` if new clients are allowed on the network
*/
} else if (!strcmp(index, "allowClients")) {
lua_pushboolean(L, !(beacon->network.attributes&UDSNETATTR_DisableConnectClients));
return 1;
// methods
} else {
for (int i=0;beaconScan_methods[i].name;i++) {
if (!strcmp(beaconScan_methods[i].name, index)) {
lua_pushcfunction(L, beaconScan_methods[i].func);
return 1;
}
}
}
}
lua_pushnil(L);
return 1;
}
/***
Return the application data of the beacon
@function :getAppData
@tparam[opt=0x4000] number maxSize maximum application data size to return
@treturn[1] string application data
@treturn[2] nil
@treturn[2] error code
*/
static int beaconScan_getAppData(lua_State *L) {
udsNetworkScanInfo* beacon = luaL_checkudata(L, 1, "LUDSBeaconScan");
size_t maxSize = luaL_optinteger(L, 2, 0x4000);
u8* data = malloc(maxSize);
if (data == NULL) luaL_error(L, "Memory allocation error");
size_t size = 0;
udsGetNetworkStructApplicationData(&beacon->network, data, maxSize, &size);
lua_pushlstring(L, (const char*)data, size);
free(data);
return 1;
}
// beaconScan object
static const struct luaL_Reg beaconScan_methods[] = {
{"__index", beaconScan___index },
{"getAppData", beaconScan_getAppData},
{NULL, NULL}
};
// module functions
static const struct luaL_Reg uds_lib[] = {
{"init", uds_init },
{"shutdown", uds_shutdown },
{"scan", uds_scan },
{"getNodesInfo ", uds_getNodesInfo },
{"getAppData", uds_getAppData },
{"connect", uds_connect },
{"available", uds_available },
{"send", uds_send },
{"receive", uds_receive },
{"disconnect", uds_disconnect },
{"createNetwork", uds_createNetwork },
{"setAppData", uds_setAppData },
{"destroyNetwork", uds_destroyNetwork },
{"ejectSpectators", uds_ejectSpectators},
{"ejectClient", uds_ejectClient },
{NULL, NULL}
};
/***
Constants
@section constants
*/
struct { char *name; int value; } uds_constants[] = {
/***
@field BROADCAST broadcast node ID
*/
{"BROADCAST", UDS_BROADCAST_NETWORKNODEID},
/***
@field HOST host node ID
*/
{"HOST", UDS_HOST_NETWORKNODEID },
/***
@field CLIENT used to specify a connection as a client
*/
{"CLIENT", UDSCONTYPE_Client },
/***
@field SPECTATOR used to specify a connection as a spectator
*/
{"SPECTATOR", UDSCONTYPE_Spectator },
{NULL, 0}
};
int luaopen_uds_lib(lua_State *L) {
luaL_newmetatable(L, "LUDSBeaconScan");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, beaconScan_methods, 0);
luaL_newlib(L, uds_lib);
for (int i = 0; uds_constants[i].name; i++) {
lua_pushinteger(L, uds_constants[i].value);
lua_setfield(L, -2, uds_constants[i].name);
}
return 1;
}
void load_uds_lib(lua_State *L) {
luaL_requiref(L, "ctr.uds", luaopen_uds_lib, 0);
}
void unload_uds_lib(lua_State *L) {
if (initStateUDS) {
udsConnectionStatus status;
udsGetConnectionStatus(&status);
switch (status.status) {
case 0x6: // connected as host
udsDestroyNetwork();
udsUnbind(&bind);
break;
case 0x9: // connected as client
case 0xA: // connected as spectator
udsDisconnectNetwork();
udsUnbind(&bind);
break;
default:
break;
}
udsExit();
initStateUDS = false;
}
}