mirror of
https://github.com/ctruLua/ctruLua.git
synced 2025-10-27 16:39:29 +00:00
Compare commits
48 commits
v1.0-beta3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a97ad0bca | |||
|
|
f118baa37c | ||
|
|
5888ee3810 | ||
|
|
3303e9783d | ||
|
|
b47971bfca | ||
|
|
4a2c1a7c68 | ||
|
|
20f8cd3cb9 | ||
|
|
5494f3d2e5 | ||
|
|
b4ceb200ea | ||
|
|
707b1a451e | ||
|
|
f554f53a47 | ||
|
|
2504fc90f1 | ||
|
|
d0fb704205 | ||
|
|
ac47b1d981 | ||
|
|
4d1e3ec455 | ||
|
|
358b68c643 | ||
|
|
2b7d37304d | ||
|
|
347e480d5a | ||
|
|
b798818e99 | ||
|
|
6b65df0b8e | ||
|
|
e7ff54d58c | ||
|
|
e87651a404 | ||
|
|
15bb00780b | ||
|
|
1f23b86379 | ||
|
|
05c9adc2a0 | ||
|
|
4669a68402 | ||
|
|
c687efcfb4 | ||
|
|
eae356ce80 | ||
|
|
6143341760 | ||
|
|
4c9bdf75fa | ||
|
|
04eb578892 | ||
|
|
6ac83ca6df | ||
|
|
5107f0277c | ||
|
|
694159f444 | ||
|
|
e5467b663d | ||
|
|
6a9fbbb133 | ||
|
|
9db21c7831 | ||
|
|
acd41db805 | ||
|
|
34c48f360e | ||
|
|
e84ab0e3b2 | ||
|
|
f180d4352d | ||
|
|
34d12eae0f | ||
|
|
60b304b2d3 | ||
|
|
cbfc7bfaca | ||
|
|
b6beaddf66 | ||
|
|
a380f09a34 | ||
|
|
3eb41b5062 | ||
|
|
116575fb5f |
59 changed files with 11546 additions and 966 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
/build/*
|
||||
/ctruLua.*
|
||||
/doc/html/*
|
||||
/doc/sublimetext/*
|
||||
|
|
|
|||
29
Makefile
29
Makefile
|
|
@ -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
|
||||
|
|
|
|||
81
README.md
81
README.md
|
|
@ -1,25 +1,86 @@
|
|||
# ctrµLua
|
||||
|
||||
Everything is in the Wiki.
|
||||

|
||||
|
||||
Warning: the 'u' in the repo's name is a 'µ', not a 'u'.
|
||||
|
||||
#### Builds 
|
||||
### 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 
|
||||
|
||||
* 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)).
|
||||
* __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
|
||||
|
||||
#### Based on ctrulib by smealum: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib)
|
||||
|
|
|
|||
|
|
@ -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
93
doc/filepicker.md
Normal 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
118
doc/ldoc.ltp
Normal 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
BIN
icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
9
libs/sf2dlib/.gitignore
vendored
9
libs/sf2dlib/.gitignore
vendored
|
|
@ -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
1
libs/sf2dlib/libsf2d/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/Default/
|
||||
|
|
@ -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)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
6615
libs/stb/include/stb_image.h
Normal file
File diff suppressed because it is too large
Load diff
1045
libs/stb/include/stb_image_write.h
Normal file
1045
libs/stb/include/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load diff
40
sdcard/3ds/ctruLua/config/keyboard.cfg
Normal file
40
sdcard/3ds/ctruLua/config/keyboard.cfg
Normal 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"
|
||||
}
|
||||
|
|
@ -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,60 +178,52 @@ 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
return type(lines) == "table" and ret or ret[1]
|
||||
end
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
318
sdcard/3ds/ctruLua/libs/filepicker.lua
Normal file
318
sdcard/3ds/ctruLua/libs/filepicker.lua
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
149
source/apt.c
149
source/apt.c
|
|
@ -13,51 +13,10 @@ 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);
|
||||
|
|
@ -70,11 +29,13 @@ static int apt_setStatus(lua_State *L) {
|
|||
/***
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +58,7 @@ static int apt_getStatusPower(lua_State *L) {
|
|||
u32 status = aptGetStatusPower();
|
||||
|
||||
lua_pushboolean(L, status);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
345
source/audio.c
345
source/audio.c
|
|
@ -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, ¤t_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 {
|
||||
|
|
@ -286,6 +410,9 @@ static int audio_loadRaw(lua_State *L) {
|
|||
audio->size = dataSize;
|
||||
audio->data = data;
|
||||
|
||||
audio->chunkSize = audio->size;
|
||||
audio->chunkNsamples = audio->nsamples;
|
||||
|
||||
audio->speed = 1.0;
|
||||
|
||||
return 1;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
source/ctr.c
67
source/ctr.c
|
|
@ -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) {
|
||||
|
|
@ -190,6 +218,33 @@ int luaopen_ctr_lib(lua_State *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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,6 @@ typedef struct {
|
|||
sftd_font *font;
|
||||
} font_userdata;
|
||||
|
||||
extern u32 textSize;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
145
source/fs.c
145
source/fs.c
|
|
@ -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;
|
||||
}
|
||||
errno = 0;
|
||||
struct dirent *entry;
|
||||
while (((entry = readdir(dir)) != NULL) && !errno) {
|
||||
lua_createtable(L, 0, 3);
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
518
source/gfx.c
518
source/gfx.c
|
|
@ -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
|
||||
|
|
@ -203,7 +211,87 @@ static int gfx_point(lua_State *L) {
|
|||
}
|
||||
|
||||
/***
|
||||
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);
|
||||
|
|
@ -221,16 +311,72 @@ 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
12
source/gfx.h
Normal 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
|
||||
23
source/hid.c
23
source/hid.c
|
|
@ -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 },
|
||||
|
|
|
|||
153
source/httpc.c
153
source/httpc.c
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
30
source/ir.c
30
source/ir.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,9 +52,32 @@ 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);
|
||||
|
|
|
|||
73
source/map.c
73
source/map.c
|
|
@ -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");
|
||||
|
|
@ -58,6 +62,15 @@ static int map_load(lua_State *L) {
|
|||
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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
source/ptm.c
33
source/ptm.c
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
source/qtm.c
13
source/qtm.c
|
|
@ -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);
|
||||
|
|
|
|||
370
source/socket.c
370
source/socket.c
|
|
@ -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;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
lua_pushboolean(L, false);
|
||||
lua_pushinteger(L, ret);
|
||||
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 (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);
|
||||
int count = luaL_optinteger(L, 2, 8191);
|
||||
|
||||
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;
|
||||
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));
|
||||
ssize_t n = sendto(userdata->socket, data, datasize, 0, (struct sockaddr *)&to, sizeof(to));
|
||||
|
||||
return 0;
|
||||
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}
|
||||
};
|
||||
|
||||
|
|
|
|||
160
source/texture.c
160
source/texture.c
|
|
@ -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);
|
||||
|
|
@ -58,9 +66,15 @@ 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) {
|
||||
|
|
@ -113,20 +127,24 @@ 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;
|
||||
|
|
@ -135,13 +153,15 @@ static int texture_draw(lua_State *L) {
|
|||
/***
|
||||
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,8 +172,10 @@ 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);
|
||||
float hotspotX = luaL_optnumber(L, 9, 0.0f);
|
||||
float hotspotY = luaL_optnumber(L, 10, 0.0f);
|
||||
|
||||
sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, texture->blendColor);
|
||||
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;
|
||||
}
|
||||
|
|
@ -192,12 +214,12 @@ 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;
|
||||
|
|
@ -246,7 +268,7 @@ 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;
|
||||
|
|
@ -254,6 +276,98 @@ static int texture_setBlendColor(lua_State *L) {
|
|||
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
589
source/uds.c
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue