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
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,4 @@ | |||
| /build/* | ||||
| /ctruLua.* | ||||
| /doc/html/* | ||||
| /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 | ||||
|  |  | |||
							
								
								
									
										83
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										83
									
								
								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)). | ||||
| 
 | ||||
| #### Based on ctrulib by smealum: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib) | ||||
| * __Smealum__ and everyone who worked on the ctrulib: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib) | ||||
| * __Xerpi__ for the [sf2dlib](https://github.com/xerpi/sf2dlib), [sftdlib](https://github.com/xerpi/sftdlib) and [sfillib](https://github.com/xerpi/sfillib) | ||||
| * __All the [Citra](https://citra-emu.org/) developers__ | ||||
| * __Everyone who worked on [DevKitARM](http://devkitpro.org/)__ | ||||
| * __Nothings__ for the [stb](https://github.com/nothings/stb) libs | ||||
| * Everyone who worked on the other libs we use | ||||
|   | ||||
|  |  | |||
|  | @ -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" | ||||
| } | ||||
|  | @ -15,4 +15,4 @@ return { | |||
| 	["keyword.control"] = hex(0xF92672FF), | ||||
| 	["keyword.operator"] = hex(0xF92672FF), | ||||
| 	["support.function"] = hex(0x66D9EFFF) | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -4,19 +4,23 @@ local gfx = require("ctr.gfx") | |||
| 
 | ||||
| -- Open libs | ||||
| local keyboard = require("keyboard") | ||||
| local openfile = require("openfile") | ||||
| local filepicker = require("filepicker") | ||||
| local color = dofile("color.lua") | ||||
| local syntax = dofile("syntax.lua") | ||||
| 
 | ||||
| -- Load data | ||||
| local font = gfx.font.load("VeraMono.ttf") | ||||
| local font = gfx.font.load(ctr.root .. "resources/VeraMono.ttf") | ||||
| 
 | ||||
| -- Open file | ||||
| local path, status = openfile("Choose a file to edit", nil, nil, "any") | ||||
| if not path then return end | ||||
| local path, binding, mode, key = filepicker(nil, {__default = { | ||||
| 		a = {filepicker.openFile, "Open"}, | ||||
| 		y = {filepicker.newFile, "New File"} | ||||
| 	} | ||||
| }) | ||||
| if not mode then return end | ||||
| local lineEnding | ||||
| local lines = {} | ||||
| if status == "exist" then | ||||
| if mode == "open" then | ||||
| 	for line in io.lines(path, "L") do | ||||
| 		if not lineEnding then lineEnding = line:match("([\n\r]+)$") end | ||||
| 		table.insert(lines, line:match("^(.-)[\n\r]*$")) | ||||
|  | @ -31,26 +35,79 @@ local coloredLines = syntax(lines, color) | |||
| 
 | ||||
| -- Variables | ||||
| local lineHeight = 10 | ||||
| local fontSize = 9 | ||||
| local cursorX, cursorY = 1, 1 | ||||
| local scrollX, scrollY = 0, 0 | ||||
| local fileModified = false | ||||
| 
 | ||||
| -- Helper functions | ||||
| local function displayedText(text) | ||||
| 	return text:gsub("\t", "    ") | ||||
| 	return text:gsub("\t", "    "), nil | ||||
| end | ||||
| local function drawTop(eye) | ||||
| 	-- Depth multiplicator. Multiply by a positive and add to x to add depth; multiply by a negative and add to x to go out of the screen. | ||||
| 	local function d(mul) return math.floor(mul * hid.pos3d() * eye) end | ||||
| 
 | ||||
| 	-- Lines | ||||
| 	local sI = math.floor(scrollY / lineHeight) | ||||
| 	if sI < 1 then sI = 1 end | ||||
| 
 | ||||
| 	local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight) | ||||
| 	if eI > #lines then eI = #lines end | ||||
| 
 | ||||
| 	for i = sI, eI, 1 do | ||||
| 		local x = -scrollX | ||||
| 		local y = -scrollY+ (i-1)*lineHeight | ||||
| 
 | ||||
| 		for _,colored in ipairs(coloredLines[i]) do | ||||
| 			local str = displayedText(colored[1]) | ||||
| 			gfx.text(x + d(#(lines[i]:match("^%s+") or "")*3), y, str, fontSize, colored[2]) | ||||
| 			x = x + font:width(str) | ||||
| 		end | ||||
| 	end | ||||
| 
 | ||||
| 	-- Cursor | ||||
| 	local curline = lines[cursorY] | ||||
| 	gfx.rectangle(-scrollX+ font:width(displayedText(curline:sub(1, (utf8.offset(curline, cursorX) or 0)-1))) + d(#(curline:match("^%s+") or "")*3),  | ||||
| 	              -scrollY+ (cursorY-1)*lineHeight, 1, lineHeight, 0, color.cursor) | ||||
| end | ||||
| 
 | ||||
| -- Set defaults | ||||
| gfx.set3D(false) | ||||
| gfx.set3D(true) | ||||
| gfx.color.setDefault(color.default) | ||||
| gfx.color.setBackground(color.background) | ||||
| gfx.font.setDefault(font) | ||||
| gfx.font.setSize(fontSize) | ||||
| 
 | ||||
| while ctr.run() do | ||||
| 	hid.read() | ||||
| 	local keys = hid.keys() | ||||
| 	 | ||||
| 	-- Keys input | ||||
| 	if keys.down.start then return end | ||||
| 	if keys.down.start then | ||||
| 		local exit = not fileModified | ||||
| 		if fileModified then | ||||
| 			while ctr.run() do | ||||
| 				hid.read() | ||||
| 				local keys = hid.keys() | ||||
| 				if keys.down.b then | ||||
| 					exit = false | ||||
| 					break | ||||
| 				elseif keys.down.a then | ||||
| 					exit = true | ||||
| 					break | ||||
| 				end | ||||
| 				gfx.start(gfx.TOP) | ||||
| 					gfx.text(3, 3, "The file was modified but not saved!") | ||||
| 					gfx.text(3, 3 + lineHeight, "Are you sure you want to exit without saving?") | ||||
| 					gfx.text(3, 3 + lineHeight*2, "Press A to exit and discard the modified file") | ||||
| 					gfx.text(3, 3 + lineHeight*3, "Press B to return to the editor") | ||||
| 				gfx.stop() | ||||
| 				gfx.render() | ||||
| 			end | ||||
| 		end | ||||
| 		if exit then break end | ||||
| 	end | ||||
| 	 | ||||
| 	if keys.down.dRight then | ||||
| 		cursorX = cursorX + 1 | ||||
|  | @ -95,22 +152,23 @@ while ctr.run() do | |||
| 			for i = 1, #lines, 1 do | ||||
| 				file:write(lines[i]..lineEnding) | ||||
| 				gfx.start(gfx.TOP) | ||||
| 				gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF) | ||||
| 				gfx.color.setDefault(color.background) | ||||
| 				gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%") | ||||
| 				gfx.color.setDefault(color.default) | ||||
| 					gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF) | ||||
| 					gfx.color.setDefault(color.background) | ||||
| 					gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%") | ||||
| 					gfx.color.setDefault(color.default) | ||||
| 				gfx.stop() | ||||
| 				gfx.render() | ||||
| 			end  | ||||
| 			file:flush() | ||||
| 			file:close() | ||||
| 			fileModified = false | ||||
| 		end | ||||
| 	end | ||||
| 	 | ||||
| 	-- Keyboard input | ||||
| 	local input = keyboard.read() | ||||
| 	if input then | ||||
| 		if input == "BACK" then | ||||
| 		if input == "\b" then | ||||
| 			if cursorX > utf8.len(lines[cursorY])+1 then cursorX = utf8.len(lines[cursorY])+1 end | ||||
| 			if cursorX > 1 then | ||||
| 				lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX-1)-1).. | ||||
|  | @ -120,64 +178,56 @@ while ctr.run() do | |||
| 				cursorX, cursorY = utf8.len(lines[cursorY-1])+1, cursorY - 1 | ||||
| 				lines[cursorY] = lines[cursorY]..lines[cursorY+1] | ||||
| 				table.remove(lines, cursorY+1) | ||||
| 				table.remove(coloredLines, cursorY+1) | ||||
| 			end | ||||
| 
 | ||||
| 			coloredLines[cursorY] = syntax(lines[cursorY], color) | ||||
| 
 | ||||
| 		elseif input == "\n" then | ||||
| 			local newline = lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1) | ||||
| 			local whitespace = lines[cursorY]:match("^%s+") | ||||
| 			if whitespace then newline = whitespace .. newline end | ||||
| 			 | ||||
| 			lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1) | ||||
| 			coloredLines[cursorY] = syntax(lines[cursorY], color) | ||||
| 			table.insert(lines, cursorY + 1, newline) | ||||
| 			table.insert(coloredLines, cursorY + 1, syntax(newline, color)) | ||||
| 			 | ||||
| 			cursorX, cursorY = whitespace and #whitespace+1 or 1, cursorY + 1 | ||||
| 
 | ||||
| 		else | ||||
| 			lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1)..input.. | ||||
| 			                 lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1) | ||||
| 			coloredLines[cursorY] = syntax(lines[cursorY], color) | ||||
| 			cursorX = cursorX + 1 | ||||
| 		end | ||||
| 		fileModified = true | ||||
| 	end | ||||
| 	 | ||||
| 	-- Draw | ||||
| 	gfx.start(gfx.TOP) | ||||
| 
 | ||||
| 		-- Lines | ||||
| 		local sI = math.floor(scrollY / lineHeight) | ||||
| 		if sI < 1 then sI = 1 end | ||||
| 		 | ||||
| 		local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight) | ||||
| 		if eI > #lines then eI = #lines end | ||||
| 		 | ||||
| 		for i = sI, eI, 1 do | ||||
| 			local x = -scrollX | ||||
| 			local y = -scrollY+ (i-1)*lineHeight | ||||
| 
 | ||||
| 			for _,colored in ipairs(coloredLines[i]) do | ||||
| 				local str = displayedText(colored[1]) | ||||
| 
 | ||||
| 				gfx.color.setDefault(colored[2]) | ||||
| 				gfx.text(x, y, str) | ||||
| 				gfx.color.setDefault(color.default) | ||||
| 
 | ||||
| 				x = x + font:width(str) | ||||
| 			end | ||||
| 		end | ||||
| 
 | ||||
| 		-- Cursor | ||||
| 		local curline = lines[cursorY] | ||||
| 		gfx.rectangle(-scrollX+ font:width(displayedText(curline:sub(1, (utf8.offset(curline, cursorX) or 0)-1))),  | ||||
| 		              -scrollY+ (cursorY-1)*lineHeight, 1, lineHeight, 0, color.cursor) | ||||
| 	local is3D = hid.pos3d() > 0 | ||||
| 
 | ||||
| 	gfx.start(gfx.TOP, gfx.LEFT) | ||||
| 		drawTop(is3D and 0 or -1) | ||||
| 	gfx.stop() | ||||
| 
 | ||||
| 	if is3D then | ||||
| 		gfx.start(gfx.TOP, gfx.RIGHT) | ||||
| 			drawTop(1) | ||||
| 		gfx.stop() | ||||
| 	end | ||||
| 	 | ||||
| 	gfx.start(gfx.BOTTOM) | ||||
| 
 | ||||
| 		gfx.text(3, 3, "FPS: "..math.ceil(gfx.getFPS())) | ||||
| 		gfx.text(3, 3 + lineHeight, "Press select to save.") | ||||
| 		gfx.text(3, 3 + lineHeight*2, "Press start to exit.") | ||||
| 		 | ||||
| 		keyboard.draw(5, 115) | ||||
| 		keyboard.draw(4, 115) | ||||
| 		 | ||||
| 	gfx.stop() | ||||
| 
 | ||||
| 	gfx.render() | ||||
| end | ||||
| 
 | ||||
| font:unload() | ||||
| font:unload() | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ local syntax = { | |||
| return function(lines, color) | ||||
| 	local ret = {} | ||||
| 
 | ||||
| 	for _,line in ipairs(lines) do | ||||
| 	for _, line in ipairs(type(lines) == "table" and lines or {lines}) do | ||||
| 		local colored = { { line, color.default } } | ||||
| 
 | ||||
| 		for _, patterns in ipairs(syntax) do | ||||
|  | @ -86,5 +86,5 @@ return function(lines, color) | |||
| 		table.insert(ret, colored) | ||||
| 	end | ||||
| 
 | ||||
| 	return ret | ||||
| end | ||||
| 	return type(lines) == "table" and ret or ret[1] | ||||
| end | ||||
|  |  | |||
|  | @ -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() | ||||
							
								
								
									
										159
									
								
								source/apt.c
									
										
									
									
									
								
							
							
						
						
									
										159
									
								
								source/apt.c
									
										
									
									
									
								
							|  | @ -13,68 +13,29 @@ Used to manage the applets and application status. | |||
| #include <lua.h> | ||||
| #include <lauxlib.h> | ||||
| 
 | ||||
| /***
 | ||||
| Initialize the APT module. Useless. | ||||
| @function init | ||||
| */ | ||||
| static int apt_init(lua_State *L) { | ||||
| 	Result ret = aptInit(); | ||||
| 	if (ret!=0) { | ||||
| 		lua_pushboolean(L, false); | ||||
| 		lua_pushinteger(L, ret); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	lua_pushboolean(L, true); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Shutdown the APT module. Useless, don't use it. | ||||
| @function shutdown | ||||
| */ | ||||
| static int apt_shutdown(lua_State *L) { | ||||
| 	aptExit(); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Open an APT session. Should only work if you don't use the homebrew menu. | ||||
| @function openSession | ||||
| */ | ||||
| static int apt_openSession(lua_State *L) { | ||||
| 	aptOpenSession(); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Close the current APT session. | ||||
| @function closeSession | ||||
| */ | ||||
| static int apt_closeSession(lua_State *L) { | ||||
| 	aptCloseSession(); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Set the app status. | ||||
| @function setStatus | ||||
| @tparam integer status the new app status | ||||
| */ | ||||
| static int apt_setStatus(lua_State *L) { | ||||
| 	APT_AppStatus status = luaL_checkinteger(L, 1); | ||||
| 	 | ||||
| 
 | ||||
| 	aptSetStatus(status); | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Get the app status. | ||||
| @function getStatus | ||||
| @treturn integer the app status | ||||
| */ | ||||
| static int apt_getStatus(lua_State *L) { | ||||
| 	APT_AppStatus status = aptGetStatus(); | ||||
| 	 | ||||
| 	lua_pushinteger(L, status); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -84,6 +45,7 @@ Return to the Home menu. | |||
| */ | ||||
| static int apt_returnToMenu(lua_State *L) { | ||||
| 	aptReturnToMenu(); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -94,8 +56,9 @@ Get the power status. | |||
| */ | ||||
| static int apt_getStatusPower(lua_State *L) { | ||||
| 	u32 status = aptGetStatusPower(); | ||||
| 	 | ||||
| 
 | ||||
| 	lua_pushboolean(L, status); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -106,9 +69,9 @@ Set the power status. | |||
| */ | ||||
| static int apt_setStatusPower(lua_State *L) { | ||||
| 	u32 status = lua_toboolean(L, 1); | ||||
| 	 | ||||
| 
 | ||||
| 	aptSetStatusPower(status); | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -118,6 +81,7 @@ Signal that the application is ready for sleeping. | |||
| */ | ||||
| static int apt_signalReadyForSleep(lua_State *L) { | ||||
| 	aptSignalReadyForSleep(); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -128,21 +92,60 @@ Return the Home menu AppID. | |||
| */ | ||||
| static int apt_getMenuAppID(lua_State *L) { | ||||
| 	lua_pushinteger(L, aptGetMenuAppID()); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Allow or not the system to enter sleep mode. | ||||
| @function setSleepAllowed | ||||
| @tparam boolean allowed `true` to allow, `false` to disallow | ||||
| */ | ||||
| static int apt_setSleepAllowed(lua_State *L) { | ||||
| 	bool allowed = lua_toboolean(L, 1); | ||||
| 
 | ||||
| 	aptSetSleepAllowed(allowed); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Check if sleep mode is allowed. | ||||
| @function isSleepAllowed | ||||
| @treturn boolean `true` is allowed, false if not. | ||||
| */ | ||||
| static int apt_isSleepAllowed(lua_State *L) { | ||||
| 	lua_pushboolean(L, aptIsSleepAllowed()); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Checks whether the system is a New 3DS. | ||||
| @function isNew3DS | ||||
| @treturn boolean `true` if it's a New3DS, false otherwise | ||||
| */ | ||||
| static int apt_isNew3DS(lua_State *L) { | ||||
| 	bool isNew3ds; | ||||
| 
 | ||||
| 	APT_CheckNew3DS(&isNew3ds); | ||||
| 
 | ||||
| 	lua_pushboolean(L, isNew3ds); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static const struct luaL_Reg apt_lib[] = { | ||||
| 	{"init",                apt_init               }, | ||||
| 	{"shutdown",            apt_shutdown           }, | ||||
| 	{"openSession",         apt_openSession        }, | ||||
| 	{"closeSession",        apt_closeSession       }, | ||||
| 	{"setStatus",           apt_setStatus          }, | ||||
| 	{"getStatus",           apt_getStatus          }, | ||||
| 	{"returnToMenu",        apt_returnToMenu       }, | ||||
| 	{"getStatusPower",      apt_getStatusPower     }, | ||||
| 	{"setStatusPower",      apt_setStatusPower     }, | ||||
| 	{"signalReadyForSleep", apt_signalReadyForSleep}, | ||||
| 	{"getMenuAppID",        apt_getMenuAppID       }, | ||||
| 	{ "setStatus",           apt_setStatus           }, | ||||
| 	{ "getStatus",           apt_getStatus           }, | ||||
| 	{ "returnToMenu",        apt_returnToMenu        }, | ||||
| 	{ "getStatusPower",      apt_getStatusPower      }, | ||||
| 	{ "setStatusPower",      apt_setStatusPower      }, | ||||
| 	{ "signalReadyForSleep", apt_signalReadyForSleep }, | ||||
| 	{ "getMenuAppID",        apt_getMenuAppID        }, | ||||
| 	{ "setSleepAllowed",     apt_setSleepAllowed     }, | ||||
| 	{ "isSleepAllowed",      apt_isSleepAllowed      }, | ||||
| 	{ "isNew3DS",            apt_isNew3DS            }, | ||||
| 	{NULL, NULL} | ||||
| }; | ||||
| 
 | ||||
|  | @ -268,37 +271,45 @@ struct { char *name; int value; } apt_constants[] = { | |||
| 	*/ | ||||
| 	{"APTSIGNAL_HOMEBUTTON",   APTSIGNAL_HOMEBUTTON  }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_PREPARESLEEP | ||||
| 	@field APTSIGNAL_HOMEBUTTON2 | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_PREPARESLEEP", APTSIGNAL_PREPARESLEEP}, | ||||
| 	{"APTSIGNAL_HOMEBUTTON2",   APTSIGNAL_HOMEBUTTON2 }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_ENTERSLEEP | ||||
| 	@field APTSIGNAL_SLEEP_QUERY | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_ENTERSLEEP",   APTSIGNAL_ENTERSLEEP  }, | ||||
| 	{"APTSIGNAL_SLEEP_QUERY",  APTSIGNAL_SLEEP_QUERY }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_SLEEP_CANCEL | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_SLEEP_CANCEL", APTSIGNAL_SLEEP_CANCEL}, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_SLEEP_ENTER | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_SLEEP_ENTER",  APTSIGNAL_SLEEP_ENTER }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_WAKEUP | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_WAKEUP",       APTSIGNAL_WAKEUP      }, | ||||
| 	{"APTSIGNAL_SLEEP_WAKEUP", APTSIGNAL_SLEEP_WAKEUP}, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_ENABLE | ||||
| 	@field APTSIGNAL_SHUTDOWN | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_ENABLE",       APTSIGNAL_ENABLE      }, | ||||
| 	{"APTSIGNAL_SHUTDOWN",     APTSIGNAL_SHUTDOWN    }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_POWERBUTTON | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_POWERBUTTON",  APTSIGNAL_POWERBUTTON }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_UTILITY | ||||
| 	@field APTSIGNAL_POWERBUTTON2 | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_UTILITY",      APTSIGNAL_UTILITY     }, | ||||
| 	{"APTSIGNAL_POWERBUTTON2", APTSIGNAL_POWERBUTTON2}, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_SLEEPSYSTEM | ||||
| 	@field APTSIGNAL_TRY_SLEEP | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_SLEEPSYSTEM",  APTSIGNAL_SLEEPSYSTEM }, | ||||
| 	{"APTSIGNAL_TRY_SLEEP",    APTSIGNAL_TRY_SLEEP   }, | ||||
| 	/***
 | ||||
| 	@field APTSIGNAL_ERROR | ||||
| 	@field APTSIGNAL_ORDERTOCLOSE | ||||
| 	*/ | ||||
| 	{"APTSIGNAL_ERROR",        APTSIGNAL_ERROR       }, | ||||
| 	{"APTSIGNAL_ORDERTOCLOSE", APTSIGNAL_ORDERTOCLOSE}, | ||||
| 	/***
 | ||||
| 	@field APTHOOK_ONSUSPEND | ||||
| 	*/ | ||||
|  | @ -327,6 +338,8 @@ struct { char *name; int value; } apt_constants[] = { | |||
| }; | ||||
| 
 | ||||
| int luaopen_apt_lib(lua_State *L) { | ||||
| 	aptInit(); | ||||
| 	 | ||||
| 	luaL_newlib(L, apt_lib); | ||||
| 	 | ||||
| 	for (int i = 0; apt_constants[i].name; i++) { | ||||
|  | @ -340,3 +353,7 @@ int luaopen_apt_lib(lua_State *L) { | |||
| void load_apt_lib(lua_State *L) { | ||||
| 	luaL_requiref(L, "ctr.apt", luaopen_apt_lib, false); | ||||
| } | ||||
| 
 | ||||
| void unload_apt_lib(lua_State *L) { | ||||
| 	aptExit(); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										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 { | ||||
|  | @ -285,6 +409,9 @@ static int audio_loadRaw(lua_State *L) { | |||
| 	audio->nsamples = dataSize/sampleSize; | ||||
| 	audio->size = dataSize; | ||||
| 	audio->data = data; | ||||
| 
 | ||||
| 	audio->chunkSize = audio->size; | ||||
| 	audio->chunkNsamples = audio->nsamples; | ||||
| 	 | ||||
| 	audio->speed = 1.0; | ||||
| 	 | ||||
|  | @ -445,7 +572,7 @@ static int audio_stop(lua_State *L) { | |||
| 	if (channel == -1) { | ||||
| 		for (int i = 0; i <= 23; i++) { | ||||
| 			if (ndspChnIsPlaying(i)) { | ||||
| 				ndspChnWaveBufClear(i); | ||||
| 				stopAudio(i); | ||||
| 				n++; | ||||
| 			} | ||||
| 		} | ||||
|  | @ -453,7 +580,7 @@ static int audio_stop(lua_State *L) { | |||
| 		luaL_error(L, "channel number must be between 0 and 23"); | ||||
| 	} else { | ||||
| 		if (ndspChnIsPlaying(channel)) { | ||||
| 			ndspChnWaveBufClear(channel); | ||||
| 			stopAudio(channel); | ||||
| 			n++; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -463,6 +590,128 @@ static int audio_stop(lua_State *L) { | |||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Update all the currently playing audio streams. | ||||
| Must be called every frame if you want to use audio with streaming. | ||||
| @function update | ||||
| */ | ||||
| static int audio_update(lua_State *L) { | ||||
| 	if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly"); | ||||
| 
 | ||||
| 	for (int i = 0; i <= 23; i++) { | ||||
| 		if (streaming[i] == NULL) continue; | ||||
| 		audio_stream* stream = streaming[i]; | ||||
| 		if (stream->done) continue; | ||||
| 		audio_userdata* audio = stream->audio; | ||||
| 
 | ||||
| 		// If the next chunk started to play, load the next one
 | ||||
| 		if (stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) == stream->nextWaveBuf->sequence_id) { | ||||
| 			if (stream->prevWaveBuf) stream->prevStartTime = stream->prevStartTime + (double)(audio->chunkNsamples) / audio->rate; | ||||
| 
 | ||||
| 			if (!stream->eof) { | ||||
| 				// Swap buffers
 | ||||
| 				char* prevData = stream->prevData; // doesn't contain important data, can rewrite
 | ||||
| 				char* nextData = stream->nextData; // contains the data that started playing
 | ||||
| 				stream->prevData = nextData; // buffer in use
 | ||||
| 				stream->nextData = prevData; // now contains an available buffer
 | ||||
| 				stream->prevWaveBuf = stream->nextWaveBuf; | ||||
| 
 | ||||
| 				// Decoding loop
 | ||||
| 				u32 chunkNsamples = audio->chunkNsamples; // chunk nsamples and size may be lower than the defaults if reached EOF
 | ||||
| 				u32 chunkSize = audio->chunkSize; | ||||
| 				if (audio->type == TYPE_OGG) { | ||||
| 					if (ov_seekable(&audio->vf) && ov_raw_tell(&audio->vf) != stream->rawPosition) | ||||
| 						ov_raw_seek(&audio->vf, stream->rawPosition); // goto last read end (audio file may be played multiple times at one)
 | ||||
| 
 | ||||
| 					int offset = 0; | ||||
| 					while (!stream->eof && offset < audio->chunkSize) { | ||||
| 						long ret = ov_read(&audio->vf, &stream->nextData[offset], fmin(audio->chunkSize - offset, 4096), &stream->currentSection); | ||||
| 						if (ret == 0) { | ||||
| 							stream->eof = 1; | ||||
| 						} else if (ret < 0) { | ||||
| 							luaL_error(L, "error in the ogg vorbis stream"); | ||||
| 							return 0; | ||||
| 						} else { | ||||
| 							offset += ret; | ||||
| 						} | ||||
| 					} | ||||
| 					stream->rawPosition = ov_raw_tell(&audio->vf); | ||||
| 					chunkSize = offset; | ||||
| 					chunkNsamples = chunkSize / audio->channels / audio->bytePerSample; | ||||
| 
 | ||||
| 				} else if (audio->type == TYPE_WAV) { | ||||
| 					chunkSize = fmin(audio->fileSize - stream->filePosition, audio->chunkSize); | ||||
| 					chunkNsamples = chunkSize / audio->channels / audio->bytePerSample; | ||||
| 
 | ||||
| 					fseek(audio->file, stream->filePosition, SEEK_SET); // goto last read end (audio file may be played multiple times at one)
 | ||||
| 					fread(stream->nextData, chunkSize, 1, audio->file); | ||||
| 					stream->filePosition = ftell(audio->file); | ||||
| 					if (stream->filePosition == audio->fileSize) stream->eof = 1; | ||||
| 
 | ||||
| 				} else luaL_error(L, "unknown audio type"); | ||||
| 
 | ||||
| 				// Send & play audio data
 | ||||
| 				ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); | ||||
| 
 | ||||
| 				waveBuf->data_vaddr = stream->nextData; | ||||
| 				waveBuf->nsamples = chunkNsamples; | ||||
| 				waveBuf->looping = false; | ||||
| 
 | ||||
| 				DSP_FlushDataCache((u32*)stream->nextData, chunkSize); | ||||
| 
 | ||||
| 				ndspChnWaveBufAdd(i, waveBuf); | ||||
| 
 | ||||
| 				stream->nextWaveBuf = waveBuf; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// Free the last chunk if it's no longer played
 | ||||
| 		if (stream->prevWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->prevWaveBuf->sequence_id) { | ||||
| 			free(stream->prevWaveBuf); | ||||
| 			stream->prevWaveBuf = NULL; | ||||
| 		} | ||||
| 
 | ||||
| 		// We're done
 | ||||
| 		if (stream->prevWaveBuf == NULL && stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->nextWaveBuf->sequence_id && stream->eof) { | ||||
| 			free(stream->nextWaveBuf); | ||||
| 			stream->nextWaveBuf = NULL; | ||||
| 
 | ||||
| 			// Free memory
 | ||||
| 			if (!stream->loop) { | ||||
| 				linearFree(stream->prevData); | ||||
| 				stream->prevData = NULL; | ||||
| 				linearFree(stream->nextData); | ||||
| 				stream->nextData = NULL; | ||||
| 				stream->done = true; | ||||
| 			// Loop: goto start
 | ||||
| 			} else { | ||||
| 				// Send & play audio initial data
 | ||||
| 				ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); | ||||
| 
 | ||||
| 				waveBuf->data_vaddr = audio->data; | ||||
| 				waveBuf->nsamples = audio->chunkNsamples; | ||||
| 				waveBuf->looping = false; | ||||
| 
 | ||||
| 				DSP_FlushDataCache((u32*)audio->data, audio->chunkSize); | ||||
| 
 | ||||
| 				ndspChnWaveBufAdd(i, waveBuf); | ||||
| 
 | ||||
| 				stream->nextWaveBuf = waveBuf; | ||||
| 
 | ||||
| 				// Reset stream values
 | ||||
| 				stream->prevStartTime = 0; | ||||
| 				stream->eof = false; | ||||
| 				if (audio->type == TYPE_OGG) { | ||||
| 					stream->currentSection = audio->currentSection; | ||||
| 					stream->rawPosition = audio->rawPosition; | ||||
| 				} else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| audio object | ||||
| @section Methods | ||||
|  | @ -505,8 +754,11 @@ static int audio_object_time(lua_State *L) { | |||
| 
 | ||||
| 	if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing
 | ||||
| 		lua_pushnumber(L, 0); | ||||
| 	else | ||||
| 		lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate); | ||||
| 	else { | ||||
| 		double additionnalTime = 0; | ||||
| 		if (streaming[channel] != NULL) additionnalTime = streaming[channel]->prevStartTime; | ||||
| 		lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate + additionnalTime); | ||||
| 	} | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
|  | @ -642,7 +894,7 @@ static int audio_object_play(lua_State *L) { | |||
| 	if (channel < 0 || channel > 23) luaL_error(L, "channel number must be between 0 and 23"); | ||||
| 
 | ||||
| 	// Set channel parameters
 | ||||
| 	ndspChnWaveBufClear(channel); | ||||
| 	stopAudio(channel); | ||||
| 	ndspChnReset(channel); | ||||
| 	ndspChnInitParams(channel); | ||||
| 	ndspChnSetMix(channel, audio->mix); | ||||
|  | @ -650,20 +902,47 @@ static int audio_object_play(lua_State *L) { | |||
| 	ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works
 | ||||
| 	ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding)); | ||||
| 
 | ||||
| 	// Send & play audio data
 | ||||
| 	// Send & play audio initial data
 | ||||
| 	ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); | ||||
| 
 | ||||
| 	waveBuf->data_vaddr = audio->data; | ||||
| 	waveBuf->nsamples = audio->nsamples; | ||||
| 	waveBuf->looping = loop; | ||||
| 	waveBuf->nsamples = audio->chunkNsamples; | ||||
| 	waveBuf->looping = (audio->chunkSize < audio->size) ? false : loop; // let ndsp loop the chunk if not streaming
 | ||||
| 	 | ||||
| 	DSP_FlushDataCache((u32*)audio->data, audio->size); | ||||
| 	DSP_FlushDataCache((u32*)audio->data, audio->chunkSize); | ||||
| 	 | ||||
| 	ndspChnWaveBufAdd(channel, waveBuf); | ||||
| 	channels[channel] = audio; | ||||
| 
 | ||||
| 	lua_pushinteger(L, channel); | ||||
| 
 | ||||
| 	// Remove last audio stream
 | ||||
| 	if (streaming[channel] != NULL) { | ||||
| 		free(streaming[channel]); | ||||
| 		streaming[channel] = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	// Stream the rest of the audio
 | ||||
| 	if (audio->chunkSize < audio->size) { | ||||
| 		audio_stream* stream = calloc(1, sizeof(audio_stream)); | ||||
| 		stream->audio = audio; | ||||
| 		stream->loop = loop; | ||||
| 		stream->nextWaveBuf = waveBuf; | ||||
| 
 | ||||
| 		// Allocate buffers
 | ||||
| 		if (linearSpaceFree() < audio->chunkSize*2) luaL_error(L, "not enough linear memory available"); | ||||
| 		stream->nextData = linearAlloc(audio->chunkSize); | ||||
| 		stream->prevData = linearAlloc(audio->chunkSize); | ||||
| 
 | ||||
| 		// Init stream values
 | ||||
| 		if (audio->type == TYPE_OGG) { | ||||
| 			stream->currentSection = audio->currentSection; | ||||
| 			stream->rawPosition = audio->rawPosition; | ||||
| 		} else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition; | ||||
| 
 | ||||
| 		streaming[channel] = stream; | ||||
| 	} | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -688,7 +967,7 @@ static int audio_object_stop(lua_State *L) { | |||
| 	if (channel == -1) { | ||||
| 		for (int i = 0; i <= 23; i++) { | ||||
| 			if (channels[i] == audio && ndspChnIsPlaying(i)) { | ||||
| 				ndspChnWaveBufClear(i); | ||||
| 				stopAudio(i); | ||||
| 				n++; | ||||
| 			} | ||||
| 		} | ||||
|  | @ -696,7 +975,7 @@ static int audio_object_stop(lua_State *L) { | |||
| 		luaL_error(L, "channel number must be between 0 and 23"); | ||||
| 	} else { | ||||
| 		if (channels[channel] == audio && ndspChnIsPlaying(channel)) { | ||||
| 			ndspChnWaveBufClear(channel); | ||||
| 			stopAudio(channel); | ||||
| 			n++; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -709,7 +988,7 @@ static int audio_object_stop(lua_State *L) { | |||
| /***
 | ||||
| Returns the audio object type. | ||||
| @function :type | ||||
| @treturn string "ogg" or "wav" | ||||
| @treturn string "ogg", "wav" or "raw" | ||||
| */ | ||||
| static int audio_object_type(lua_State *L) { | ||||
| 	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio"); | ||||
|  | @ -718,6 +997,10 @@ static int audio_object_type(lua_State *L) { | |||
| 		lua_pushstring(L, "ogg"); | ||||
| 	else if (audio->type == TYPE_WAV) | ||||
| 		lua_pushstring(L, "wav"); | ||||
| 	else if (audio->type == TYPE_RAW) | ||||
| 		lua_pushstring(L, "raw"); | ||||
| 	else | ||||
| 		lua_pushstring(L, "unknown"); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
|  | @ -733,12 +1016,13 @@ static int audio_object_unload(lua_State *L) { | |||
| 	if (isAudioInitialized) { | ||||
| 		for (int i = 0; i <= 23; i++) { | ||||
| 			if (channels[i] == audio) { | ||||
| 				ndspChnWaveBufClear(i); | ||||
| 				stopAudio(i); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (audio->type == TYPE_OGG) ov_clear(&audio->vf); | ||||
| 	else if (audio->type == TYPE_WAV) fclose(audio->file); | ||||
| 
 | ||||
| 	// Free memory
 | ||||
| 	linearFree(audio->data); | ||||
|  | @ -872,6 +1156,7 @@ static const struct luaL_Reg audio_lib[] = { | |||
| 	{ "interpolation", audio_interpolation }, | ||||
| 	{ "speed",         audio_speed         }, | ||||
| 	{ "stop",          audio_stop          }, | ||||
| 	{ "update",        audio_update        }, | ||||
| 	{ NULL, NULL } | ||||
| }; | ||||
| 
 | ||||
|  | @ -887,9 +1172,7 @@ int luaopen_audio_lib(lua_State *L) { | |||
| } | ||||
| 
 | ||||
| void load_audio_lib(lua_State *L) { | ||||
| 	if (!isAudioInitialized) { | ||||
| 		isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
 | ||||
| 	} | ||||
| 	if (!isAudioInitialized) isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success
 | ||||
| 
 | ||||
| 	luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false); | ||||
| } | ||||
|  |  | |||
|  | @ -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) { | ||||
|  | @ -189,6 +217,33 @@ int luaopen_ctr_lib(lua_State *L) { | |||
| 		ctr_libs[i].load(L); | ||||
| 		lua_setfield(L, -2, ctr_libs[i].name); | ||||
| 	} | ||||
| 	 | ||||
| 	/***
 | ||||
| 	Running version of ctrµLua. This string contains the exact name of the last (pre-)release tag. | ||||
| 	@field version | ||||
| 	*/ | ||||
| 	lua_pushstring(L, CTR_VERSION); | ||||
| 	lua_setfield(L, -2, "version"); | ||||
| 	/***
 | ||||
| 	Running build of ctrµLua. This string contains the last commit hash. | ||||
| 	@field build | ||||
| 	*/ | ||||
| 	lua_pushstring(L, CTR_BUILD); | ||||
| 	lua_setfield(L, -2, "build"); | ||||
| 	 | ||||
| 	/***
 | ||||
| 	Root directory of ctrµLua. Contains the working directory where ctrµLua has been launched OR the romfs root if romfs has been enabled. | ||||
| 	@field root | ||||
| 	*/ | ||||
| 	#ifdef ROMFS | ||||
| 	char* buff = "romfs:/"; | ||||
| 	chdir(buff); | ||||
| 	#else | ||||
| 	char* buff = malloc(1024); | ||||
| 	getcwd(buff, 1024); | ||||
| 	#endif | ||||
| 	lua_pushstring(L, buff); | ||||
| 	lua_setfield(L, -2, "root"); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										151
									
								
								source/fs.c
									
										
									
									
									
								
							
							
						
						
									
										151
									
								
								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; | ||||
| 	} | ||||
| 
 | ||||
| 	FS_Path dirPath = fsMakePath(PATH_ASCII, path); | ||||
| 
 | ||||
| 	Handle dirHandle; | ||||
| 	FSUSER_OpenDirectory(&dirHandle, archive, dirPath); | ||||
| 
 | ||||
| 	u32 entriesRead = 0; | ||||
| 	do { | ||||
| 		FS_DirectoryEntry buffer; | ||||
| 
 | ||||
| 		FSDIR_Read(dirHandle, &entriesRead, 1, &buffer); | ||||
| 
 | ||||
| 		if (!entriesRead) break; | ||||
| 
 | ||||
| 		uint8_t name[0x106+1]; // utf8 file name
 | ||||
| 		size_t size = utf16_to_utf8(name, buffer.name, 0x106); | ||||
| 		*(name+size) = 0x0; // mark text end
 | ||||
| 
 | ||||
| 		lua_createtable(L, 0, 8); | ||||
| 
 | ||||
| 		lua_pushstring(L, (const char *)name); | ||||
| 	errno = 0; | ||||
| 	struct dirent *entry; | ||||
| 	while (((entry = readdir(dir)) != NULL) && !errno) { | ||||
| 		lua_createtable(L, 0, 3); | ||||
| 		 | ||||
| 		lua_pushstring(L, (const char*)entry->d_name); | ||||
| 		lua_setfield(L, -2, "name"); | ||||
| 		lua_pushstring(L, (const char *)buffer.shortName); | ||||
| 		lua_setfield(L, -2, "shortName"); | ||||
| 		lua_pushstring(L, (const char *)buffer.shortExt); | ||||
| 		lua_setfield(L, -2, "shortExt"); | ||||
| 		lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_DIRECTORY); | ||||
| 		lua_pushboolean(L, entry->d_type==DT_DIR); | ||||
| 		lua_setfield(L, -2, "isDirectory"); | ||||
| 		lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_HIDDEN); | ||||
| 		lua_setfield(L, -2, "isHidden"); | ||||
| 		lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_ARCHIVE); | ||||
| 		lua_setfield(L, -2, "isArchive"); | ||||
| 		lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_READ_ONLY); | ||||
| 		lua_setfield(L, -2, "isReadOnly"); | ||||
| 		lua_pushinteger(L, buffer.fileSize); | ||||
| 		lua_setfield(L, -2, "fileSize"); | ||||
| 		 | ||||
| 		if (entry->d_type==DT_REG) { // Regular files: check size
 | ||||
| 			char* filepath = malloc(strlen(path)+strlen(entry->d_name)+1); | ||||
| 			if (filepath == NULL) | ||||
| 				luaL_error(L, "Memory allocation error"); | ||||
| 			strcpy(filepath, path); | ||||
| 			strcat(filepath, entry->d_name); | ||||
| 
 | ||||
| 			struct stat stats; | ||||
| 			if (stat(filepath, &stats)) { | ||||
| 				free(filepath); | ||||
| 				if (shouldFreePath) free(path); | ||||
| 				luaL_error(L, "Stat error: %s (%d)", strerror(errno), errno); | ||||
| 				return 0; | ||||
| 			} else { | ||||
| 				lua_pushinteger(L, stats.st_size); | ||||
| 			} | ||||
| 			free(filepath); | ||||
| 		} else { // Everything else: 0 bytes
 | ||||
| 			lua_pushinteger(L, 0); | ||||
| 		} | ||||
| 		lua_setfield(L, -2, "size"); | ||||
| 
 | ||||
| 		lua_seti(L, -2, i); | ||||
| 		i++; | ||||
| 
 | ||||
| 	} while (entriesRead > 0); | ||||
| 
 | ||||
| 	FSDIR_Close(dirHandle); | ||||
| 	} | ||||
| 	 | ||||
| 	closedir(dir); | ||||
| 	if (shouldFreePath) free(path); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
|  | @ -156,9 +148,9 @@ Get the current working directory. | |||
| @treturn string the current working directory | ||||
| */ | ||||
| static int fs_getDirectory(lua_State *L) { | ||||
| 	char cwd[256]; | ||||
| 	char cwd[1024]; | ||||
| 
 | ||||
| 	lua_pushstring(L, getcwd(cwd, 256)); | ||||
| 	lua_pushstring(L, getcwd(cwd, 1024)); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
|  | @ -214,16 +206,9 @@ int luaopen_fs_lib(lua_State *L) { | |||
| 
 | ||||
| void load_fs_lib(lua_State *L) { | ||||
| 	if (!isFsInitialized) { | ||||
| 		fsInit(); | ||||
| 
 | ||||
| 		fsuHandle = fsGetSessionHandle(); | ||||
| 		FSUSER_Initialize(*fsuHandle); | ||||
| 
 | ||||
| 		sdmcArchive = (FS_Archive){ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")}; | ||||
| 		FSUSER_OpenArchive(&sdmcArchive); | ||||
| 		sdmcInit(); | ||||
| 		#ifdef ROMFS | ||||
| 		romfsArchive = (FS_Archive){ARCHIVE_ROMFS, fsMakePath(PATH_EMPTY, "")}; | ||||
| 		FSUSER_OpenArchive(&romfsArchive); | ||||
| 		romfsInit(); | ||||
| 		#endif | ||||
| 		isFsInitialized = true; | ||||
| 	} | ||||
|  | @ -232,10 +217,8 @@ void load_fs_lib(lua_State *L) { | |||
| } | ||||
| 
 | ||||
| void unload_fs_lib(lua_State *L) { | ||||
| 	FSUSER_CloseArchive(&sdmcArchive); | ||||
| 	sdmcExit(); | ||||
| 	#ifdef ROMFS | ||||
| 	FSUSER_CloseArchive(&romfsArchive); | ||||
| 	romfsExit(); | ||||
| 	#endif | ||||
| 
 | ||||
| 	fsExit(); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										524
									
								
								source/gfx.c
									
										
									
									
									
								
							
							
						
						
									
										524
									
								
								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 | ||||
|  | @ -194,16 +202,96 @@ Draw a point, a single pixel, on the current screen. | |||
| static int gfx_point(lua_State *L) { | ||||
| 	int x = luaL_checkinteger(L, 1); | ||||
| 	int y = luaL_checkinteger(L, 2); | ||||
| 	 | ||||
| 
 | ||||
| 	u32 color = luaL_optinteger(L, 3, color_default); | ||||
| 	 | ||||
| 
 | ||||
| 	sf2d_draw_rectangle(x, y, 1, 1, color); // well, it looks like a point
 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a rectangle on the current screen. | ||||
| Draw a line on the current screen. | ||||
| @function line | ||||
| @tparam integer x1 line's starting point horizontal coordinate, in pixels | ||||
| @tparam integer y1 line's starting point vertical coordinate, in pixels | ||||
| @tparam integer x2 line's endpoint horizontal coordinate, in pixels | ||||
| @tparam integer y2 line's endpoint vertical coordinate, in pixels | ||||
| @tparam[opt=1] number width line's thickness, in pixels | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| */ | ||||
| static int gfx_line(lua_State *L) { | ||||
| 	int x1 = luaL_checkinteger(L, 1); | ||||
| 	int y1 = luaL_checkinteger(L, 2); | ||||
| 	int x2 = luaL_checkinteger(L, 3); | ||||
| 	int y2 = luaL_checkinteger(L, 4); | ||||
| 	float width = luaL_optnumber(L, 5, 1.0f); | ||||
| 	 | ||||
| 	u32 color = luaL_optinteger(L, 6, color_default); | ||||
| 	 | ||||
| 	sf2d_draw_line(x1, y1, x2, y2, width, color); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a filled triangle on the current screen. | ||||
| @function triangle | ||||
| @tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer y2 vertical coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer x3 horizontal coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer y3 vertical coordinate of a vertex of the triangle, in pixels | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| */ | ||||
| static int gfx_triangle(lua_State *L) { | ||||
| 	int x1 = luaL_checkinteger(L, 1); | ||||
| 	int y1 = luaL_checkinteger(L, 2); | ||||
| 	int x2 = luaL_checkinteger(L, 3); | ||||
| 	int y2 = luaL_checkinteger(L, 4); | ||||
| 	int x3 = luaL_checkinteger(L, 5); | ||||
| 	int y3 = luaL_checkinteger(L, 6); | ||||
| 	 | ||||
| 	u32 color = luaL_optinteger(L, 7, color_default); | ||||
| 	 | ||||
| 	sf2d_draw_triangle(x1, y1, x2, y2, x3, y3, color); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a triangle outline on the current screen. | ||||
| @function linedTriangle | ||||
| @tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer y2 vertical coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer x3 horizontal coordinate of a vertex of the triangle, in pixels | ||||
| @tparam integer y3 vertical coordinate of a vertex of the triangle, in pixels | ||||
| @tparam[opt=1] number lineWidth line's thickness, in pixels | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| */ | ||||
| static int gfx_linedTriangle(lua_State *L) { | ||||
| 	int x1 = luaL_checkinteger(L, 1); | ||||
| 	int y1 = luaL_checkinteger(L, 2); | ||||
| 	int x2 = luaL_checkinteger(L, 3); | ||||
| 	int y2 = luaL_checkinteger(L, 4); | ||||
| 	int x3 = luaL_checkinteger(L, 5); | ||||
| 	int y3 = luaL_checkinteger(L, 6); | ||||
| 	float lineWidth = luaL_optnumber(L, 7, 1.0f); | ||||
| 
 | ||||
| 	u32 color = luaL_optinteger(L, 8, color_default); | ||||
| 
 | ||||
| 	sf2d_draw_line(x1, y1, x2, y2, lineWidth, color); | ||||
| 	sf2d_draw_line(x2, y2, x3, y3, lineWidth, color); | ||||
| 	sf2d_draw_line(x3, y3, x1, y1, lineWidth, color); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a filled rectangle on the current screen. | ||||
| @function rectangle | ||||
| @tparam integer x rectangle origin horizontal coordinate, in pixels | ||||
| @tparam integer y rectangle origin vertical coordinate, in pixels | ||||
|  | @ -211,6 +299,8 @@ Draw a rectangle on the current screen. | |||
| @tparam integer height rectangle height, in pixels | ||||
| @tparam[opt=0] number angle rectangle rotation, in radians | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| @tparam[opt] integer color2 Second drawing color ; if the argument is not nil, the rectangle will be filled with a gradient from color to color2 | ||||
| @tparam[opt] integer direction Gradient drawing direction (`gfx.TOP_TO_BOTTOM` or `gfx.LEFT_TO_RIGHT`). This argument is mandatory if a second color was specified. | ||||
| */ | ||||
| static int gfx_rectangle(lua_State *L) { | ||||
| 	int x = luaL_checkinteger(L, 1); | ||||
|  | @ -220,17 +310,73 @@ static int gfx_rectangle(lua_State *L) { | |||
| 
 | ||||
| 	float angle = luaL_optnumber(L, 5, 0); | ||||
| 	u32 color = luaL_optinteger(L, 6, color_default); | ||||
| 	 | ||||
| 	if (angle == 0) | ||||
| 		sf2d_draw_rectangle(x, y, width, height, color); | ||||
| 	else | ||||
| 		sf2d_draw_rectangle_rotate(x, y, width, height, color, angle); | ||||
| 
 | ||||
| 	// Not second color : fill with plain color.
 | ||||
| 	if (lua_isnoneornil(L, 7)) { | ||||
| 		if (angle == 0) | ||||
| 			sf2d_draw_rectangle(x, y, width, height, color); | ||||
| 		else | ||||
| 			sf2d_draw_rectangle_rotate(x, y, width, height, color, angle); | ||||
| 	// Two colors : fill with a gradient.
 | ||||
| 	} else { | ||||
| 		u32 color2 = luaL_checkinteger(L, 7); | ||||
| 		u8 direction = luaL_checkinteger(L, 8); | ||||
| 
 | ||||
| 		if (angle == 0) | ||||
| 			sf2d_draw_rectangle_gradient(x, y, width, height, color, color2, direction); | ||||
| 		else | ||||
| 			sf2d_draw_rectangle_gradient_rotate(x, y, width, height, color, color2, direction, angle); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a circle on the current screen. | ||||
| Draw a rectangle outline on the current screen. | ||||
| @function linedRectangle | ||||
| @tparam integer x rectangle origin horizontal coordinate, in pixels | ||||
| @tparam integer y rectangle origin vertical coordinate, in pixels | ||||
| @tparam integer width rectangle width, in pixels | ||||
| @tparam integer height rectangle height, in pixels | ||||
| @tparam[opt=1] integer lineWidth line's thickness, in pixels | ||||
| @tparam[opt=0] number angle rectangle rotation, in radians | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| */ | ||||
| static int gfx_linedRectangle(lua_State *L) { | ||||
| 	int x = luaL_checkinteger(L, 1); | ||||
| 	int y = luaL_checkinteger(L, 2); | ||||
| 	int width = luaL_checkinteger(L, 3); | ||||
| 	int height = luaL_checkinteger(L, 4); | ||||
| 	float lineWidth = luaL_optnumber(L, 5, 1.0f); | ||||
| 
 | ||||
| 	float angle = luaL_optnumber(L, 6, 0); | ||||
| 	u32 color = luaL_optinteger(L, 7, color_default); | ||||
| 
 | ||||
| 	// Corner coordinates
 | ||||
| 	int x2 = x + width, y2 = y; | ||||
| 	int x3 = x2, y3 = y + height; | ||||
| 	int x4 = x, y4 = y3; | ||||
| 
 | ||||
| 	// Rotate corners
 | ||||
| 	if (angle != 0) { | ||||
| 		int cx = x + width/2, cy = y + height/2; | ||||
| 		rotatePoint(x,  y,  cx, cy, angle, &x,  &y ); | ||||
| 		rotatePoint(x2, y2, cx, cy, angle, &x2, &y2); | ||||
| 		rotatePoint(x3, y3, cx, cy, angle, &x3, &y3); | ||||
| 		rotatePoint(x4, y4, cx, cy, angle, &x4, &y4); | ||||
| 	} | ||||
| 
 | ||||
| 	// Draw lines
 | ||||
| 	sf2d_draw_line(x,  y,  x2, y2, lineWidth, color); | ||||
| 	sf2d_draw_line(x2, y2, x3, y3, lineWidth, color); | ||||
| 	sf2d_draw_line(x3, y3, x4, y4, lineWidth, color); | ||||
| 	sf2d_draw_line(x4, y4, x,  y,  lineWidth, color); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a filled circle on the current screen. | ||||
| @function circle | ||||
| @tparam integer x circle center horizontal coordinate, in pixels | ||||
| @tparam integer y circle center vertical coordinate, in pixels | ||||
|  | @ -249,13 +395,63 @@ static int gfx_circle(lua_State *L) { | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a circle outline on the current screen. | ||||
| @function linedCircle | ||||
| @tparam integer x circle center horizontal coordinate, in pixels | ||||
| @tparam integer y circle center vertical coordinate, in pixels | ||||
| @tparam integer radius circle radius, in pixels | ||||
| @tparam[opt=1] integer width line's thickness, in pixels | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| */ | ||||
| static int gfx_linedCircle(lua_State *L) { | ||||
| 	int x0 = luaL_checkinteger(L, 1); | ||||
| 	int y0 = luaL_checkinteger(L, 2); | ||||
| 	int radius = luaL_checkinteger(L, 3); | ||||
| 	float width = luaL_optnumber(L, 4, 1.0f); | ||||
| 
 | ||||
| 	u32 color = luaL_optinteger(L, 5, color_default); | ||||
| 
 | ||||
| 	for (int r = ceil(radius - width/2), maxr = ceil(radius + width/2)-1; r <= maxr; r++) { | ||||
| 		// Implementatin of the Andres circle algorithm.
 | ||||
| 		int x = 0; | ||||
| 		int y = r; | ||||
| 		int d = r - 1; | ||||
| 		while (y >= x) { | ||||
| 			// Best way to draw a lot of points, 10/10
 | ||||
| 			sf2d_draw_rectangle(x0 + x , y0 + y, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 + y , y0 + x, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 - x , y0 + y, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 - y , y0 + x, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 + x , y0 - y, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 + y , y0 - x, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 - x , y0 - y, 1, 1, color); | ||||
| 			sf2d_draw_rectangle(x0 - y , y0 - x, 1, 1, color); | ||||
| 
 | ||||
| 			if (d >= 2*x) { | ||||
| 				d -= 2*x + 1; | ||||
| 				x++; | ||||
| 			} else if (d < 2*(r-y)) { | ||||
| 				d += 2*y - 1; | ||||
| 				y--; | ||||
| 			} else { | ||||
| 				d += 2*(y - x - 1); | ||||
| 				y--; | ||||
| 				x++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a text on the current screen. | ||||
| @function text | ||||
| @tparam integer x text drawing origin horizontal coordinate, in pixels | ||||
| @tparam integer y text drawing origin vertical coordinate, in pixels | ||||
| @tparam string text the text to draw | ||||
| @tparam[opt=9] integer size drawing size, in pixels | ||||
| @tparam[opt=default size] integer size drawing size, in pixels | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| @tparam[opt=default font] font font to use | ||||
| */ | ||||
|  | @ -265,7 +461,7 @@ static int gfx_text(lua_State *L) { | |||
| 	size_t len; | ||||
| 	const char *text = luaL_checklstring(L, 3, &len); | ||||
| 
 | ||||
| 	int size = luaL_optinteger(L, 4, 9); | ||||
| 	int size = luaL_optinteger(L, 4, textSize); | ||||
| 	u32 color = luaL_optinteger(L, 5, color_default); | ||||
| 	font_userdata *font = luaL_testudata(L, 6, "LFont"); | ||||
| 	if (font == NULL) { | ||||
|  | @ -293,7 +489,7 @@ Warning: No UTF32 support. | |||
| @tparam integer y text drawing origin vertical coordinate, in pixels | ||||
| @tparam string text the text to draw | ||||
| @tparam integer width width of a line, in pixels | ||||
| @tparam[opt=9] integer size drawing size, in pixels | ||||
| @tparam[opt=default Size] integer size drawing size, in pixels | ||||
| @tparam[opt=default color] integer color drawing color | ||||
| @tparam[opt=default font] font font to use | ||||
| */ | ||||
|  | @ -304,7 +500,7 @@ static int gfx_wrappedText(lua_State *L) { | |||
| 	const char *text = luaL_checklstring(L, 3, &len); | ||||
| 	unsigned int lineWidth = luaL_checkinteger(L, 4); | ||||
| 	 | ||||
| 	int size = luaL_optinteger(L, 5, 9); | ||||
| 	int size = luaL_optinteger(L, 5, textSize); | ||||
| 	u32 color = luaL_optinteger(L, 6, color_default); | ||||
| 	font_userdata *font = luaL_testudata(L, 7, "LFont"); | ||||
| 	if (font == NULL) { | ||||
|  | @ -330,7 +526,7 @@ Calculate the size of a text draw with `wrappedText`. | |||
| @function calcBoundingBox | ||||
| @tparam string text The text to check | ||||
| @tparam integer lineWidth width of a line, in pixels | ||||
| @tparam[opt=9] integer size drawing size, in pixels | ||||
| @tparam[opt=default size] integer size drawing size, in pixels | ||||
| @tparam[opt=default font] font font to use | ||||
| @treturn integer width of the text, in pixels | ||||
| @treturn integer height of the text, in pixels | ||||
|  | @ -339,7 +535,7 @@ static int gfx_calcBoundingBox(lua_State *L) { | |||
| 	size_t len; | ||||
| 	const char *text = luaL_checklstring(L, 1, &len); | ||||
| 	unsigned int lineWidth = luaL_checkinteger(L, 2); | ||||
| 	int size = luaL_optinteger(L, 3, 9); | ||||
| 	int size = luaL_optinteger(L, 3, textSize); | ||||
| 	font_userdata *font = luaL_testudata(L, 4, "LFont"); | ||||
| 	if (font == NULL) { | ||||
| 		lua_getfield(L, LUA_REGISTRYINDEX, "LFontDefault"); | ||||
|  | @ -356,6 +552,192 @@ static int gfx_calcBoundingBox(lua_State *L) { | |||
| 	return 2; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Enables or disable the scissor test. | ||||
| When the scissor test is enabled, the drawing area will be limited to a specific rectangle, every pixel drawn outside will be discarded. | ||||
| Calls this function without argument to disable the scissor test. | ||||
| @function scissor | ||||
| @tparam integer x scissor rectangle origin horizontal coordinate, in pixels | ||||
| @tparam integer y scissor rectangle origin vertical coordinate, in pixels | ||||
| @tparam integer width scissor rectangle width, in pixels | ||||
| @tparam integer height scissor rectangle height, in pixels | ||||
| @tparam[opt=false] boolean invert if true the scissor will be inverted (will draw only outside of the rectangle) | ||||
| */ | ||||
| static int gfx_scissor(lua_State *L) { | ||||
| 	if (lua_gettop(L) == 0) { | ||||
| 		lua_scissor.mode = GPU_SCISSOR_DISABLE; | ||||
| 		lua_scissor.x = 0; | ||||
| 		lua_scissor.y = 0; | ||||
| 		lua_scissor.width = 0; | ||||
| 		lua_scissor.height = 0; | ||||
| 	} else { | ||||
| 		lua_scissor.x = luaL_checkinteger(L, 1); | ||||
| 		lua_scissor.y = luaL_checkinteger(L, 2); | ||||
| 		lua_scissor.width = luaL_checkinteger(L, 3); | ||||
| 		lua_scissor.height = luaL_checkinteger(L, 4); | ||||
| 		lua_scissor.mode = lua_toboolean(L, 5) ? GPU_SCISSOR_INVERT : GPU_SCISSOR_NORMAL; | ||||
| 	} | ||||
| 
 | ||||
| 	sf2d_set_scissor_test(lua_scissor.mode, lua_scissor.x, lua_scissor.y, lua_scissor.width, lua_scissor.height); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| __Work in progress__. Create a render target. Don't use it. | ||||
| @function target | ||||
| @tparam integer width | ||||
| @tparam integer height | ||||
| @treturn target | ||||
| */ | ||||
| static int gfx_target(lua_State *L) { | ||||
| 	int width = luaL_checkinteger(L, 1); | ||||
| 	int height = luaL_checkinteger(L, 2); | ||||
| 	int wpo2 = 0, hpo2 = 0; | ||||
| 	for (;width>pow(2,wpo2);wpo2++); | ||||
| 	width = pow(2,wpo2); | ||||
| 	for (;height>pow(2,hpo2);hpo2++); | ||||
| 	height = pow(2,hpo2); | ||||
| 	 | ||||
| 	target_userdata *target; | ||||
| 	target = (target_userdata*)lua_newuserdata(L, sizeof(*target)); | ||||
| 	 | ||||
| 	luaL_getmetatable(L, "LTarget"); | ||||
| 	lua_setmetatable(L, -2); | ||||
| 	 | ||||
| 	target->target = sf2d_create_rendertarget(width, height); | ||||
| 	 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Render targets | ||||
| @section target | ||||
| */ | ||||
| 
 | ||||
| /***
 | ||||
| Clear a target to a specified color. | ||||
| @function :clear | ||||
| @tparam[opt=default color] integer color color to fill the target with | ||||
| */ | ||||
| static int gfx_target_clear(lua_State *L) { | ||||
| 	target_userdata *target = luaL_checkudata(L, 1, "LTarget"); | ||||
| 	u32 color = luaL_optinteger(L, 2, color_default); | ||||
| 	 | ||||
| 	sf2d_clear_target(target->target, color); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Destroy a target. | ||||
| @function :destroy | ||||
| */ | ||||
| static int gfx_target_destroy(lua_State *L) { | ||||
| 	target_userdata *target = luaL_checkudata(L, 1, "LTarget"); | ||||
| 	 | ||||
| 	sf2d_free_target(target->target); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct luaL_Reg target_methods[]; | ||||
| /***
 | ||||
| 
 | ||||
| */ | ||||
| static int gfx_target___index(lua_State *L) { | ||||
| 	target_userdata *target = luaL_checkudata(L, 1, "LTarget"); | ||||
| 	const char* name = luaL_checkstring(L, 2); | ||||
| 	 | ||||
| 	if (strcmp(name, "texture") == 0) { | ||||
| 		texture_userdata *texture; | ||||
| 		texture = (texture_userdata*)lua_newuserdata(L, sizeof(*texture)); | ||||
| 		luaL_getmetatable(L, "LTexture"); | ||||
| 		lua_setmetatable(L, -2); | ||||
| 		 | ||||
| 		texture->texture = &(target->target->texture); | ||||
| 		texture->scaleX = 1.0f; | ||||
| 		texture->scaleY = 1.0f; | ||||
| 		texture->blendColor = 0xffffffff; | ||||
| 		 | ||||
| 		return 1; | ||||
| 	} else if (strcmp(name, "duck") == 0) { | ||||
| 		sf2d_rendertarget *target = sf2d_create_rendertarget(64, 64); | ||||
| 		for(int i=0;;i++) { | ||||
| 			sf2d_clear_target(target, 0xff000000); | ||||
| 			sf2d_start_frame_target(target); | ||||
| 			sf2d_draw_fill_circle(i%380, i%200, 10, 0xff0000ff); | ||||
| 			sf2d_end_frame(); | ||||
| 			//sf2d_texture_tile32(&target->texture);
 | ||||
| 			 | ||||
| 			sf2d_start_frame(GFX_TOP, GFX_LEFT); | ||||
| 			sf2d_draw_texture(&target->texture, 10, 10); | ||||
| 			sf2d_end_frame(); | ||||
| 			sf2d_swapbuffers(); | ||||
| 		} | ||||
| 	} else { | ||||
| 		for (u8 i=0;target_methods[i].name;i++) { | ||||
| 			if (strcmp(target_methods[i].name, name) == 0) { | ||||
| 				lua_pushcfunction(L, target_methods[i].func); | ||||
| 				return 1; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	lua_pushnil(L); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Console | ||||
| @section console | ||||
| */ | ||||
| 
 | ||||
| /***
 | ||||
| Initialize the console. You can print on it using print(), or any function that normally outputs to stdout. | ||||
| Warning: you can't use a screen for both a console and drawing, you have to disable the console first. | ||||
| @function console | ||||
| @tparam[opt=gfx.TOP] number screen screen to draw the console on. | ||||
| @tparam[opt=false] boolean debug enable stderr output on the console | ||||
| */ | ||||
| u8 consoleScreen = GFX_TOP; | ||||
| static int gfx_console(lua_State *L) { | ||||
| 	consoleScreen = luaL_optinteger(L, 1, GFX_TOP); | ||||
| 	bool err = false; | ||||
| 	if (lua_isboolean(L, 2)) { | ||||
| 		err = lua_toboolean(L, 2); | ||||
| 	} | ||||
| 	 | ||||
| 	consoleInit(consoleScreen, NULL); | ||||
| 	if (err) | ||||
| 		consoleDebugInit(debugDevice_CONSOLE); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Clear the console. | ||||
| @function clearConsole | ||||
| */ | ||||
| static int gfx_clearConsole(lua_State *L) { | ||||
| 	consoleClear(); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Disable the console. | ||||
| @function disableConsole | ||||
| */ | ||||
| static int gfx_disableConsole(lua_State *L) { | ||||
| 	gfxSetScreenFormat(consoleScreen, GSP_BGR8_OES); | ||||
| 	gfxSetDoubleBuffering(consoleScreen, true); | ||||
| 	gfxSwapBuffersGpu(); | ||||
| 	gspWaitForVBlank(); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| // Functions
 | ||||
| static const struct luaL_Reg gfx_lib[] = { | ||||
| 	{ "start",           gfx_start           }, | ||||
|  | @ -367,66 +749,97 @@ static const struct luaL_Reg gfx_lib[] = { | |||
| 	{ "setVBlankWait",   gfx_setVBlankWait   }, | ||||
| 	{ "waitForVBlank",   gfx_waitForVBlank   }, | ||||
| 	{ "vramSpaceFree",   gfx_vramSpaceFree   }, | ||||
| 	{ "line",            gfx_line            }, | ||||
| 	{ "point",           gfx_point           }, | ||||
| 	{ "line",            gfx_line            }, | ||||
| 	{ "triangle",        gfx_triangle        }, | ||||
| 	{ "linedTriangle",   gfx_linedTriangle   }, | ||||
| 	{ "rectangle",       gfx_rectangle       }, | ||||
| 	{ "linedRectangle",  gfx_linedRectangle  }, | ||||
| 	{ "circle",          gfx_circle          }, | ||||
| 	{ "linedCircle",     gfx_linedCircle     }, | ||||
| 	{ "text",            gfx_text            }, | ||||
| 	{ "wrappedText",     gfx_wrappedText     }, | ||||
| 	{ "calcBoundingBox", gfx_calcBoundingBox }, | ||||
| 	{ "scissor",         gfx_scissor         }, | ||||
| 	{ "target",          gfx_target          }, | ||||
| 	{ "console",         gfx_console         }, | ||||
| 	{ "clearConsole",    gfx_clearConsole    }, | ||||
| 	{ "disableConsole",  gfx_disableConsole  }, | ||||
| 	{ NULL, NULL } | ||||
| }; | ||||
| 
 | ||||
| // Constants
 | ||||
| // Render target
 | ||||
| static const struct luaL_Reg target_methods[] = { | ||||
| 	{ "__index",  gfx_target___index }, | ||||
| 	{ "clear",    gfx_target_clear   }, | ||||
| 	{ "destroy",  gfx_target_destroy }, | ||||
| 	{ "__gc",     gfx_target_destroy }, | ||||
| 	{ NULL, NULL } | ||||
| }; | ||||
| 
 | ||||
| /***
 | ||||
| Constants | ||||
| @section constants | ||||
| */ | ||||
| struct { char *name; int value; } gfx_constants[] = { | ||||
| 	/***
 | ||||
| 	Constant used to select the top screen. | ||||
| 	It is equal to `0`. | ||||
| 	@field TOP | ||||
| 	*/ | ||||
| 	{ "TOP",           GFX_TOP    }, | ||||
| 	{ "TOP",           GFX_TOP            }, | ||||
| 	/***
 | ||||
| 	Constant used to select the bottom screen. | ||||
| 	It is equal to `1`. | ||||
| 	@field BOTTOM | ||||
| 	*/ | ||||
| 	{ "BOTTOM",        GFX_BOTTOM }, | ||||
| 	{ "BOTTOM",        GFX_BOTTOM         }, | ||||
| 	/***
 | ||||
| 	Constant used to select the left eye. | ||||
| 	It is equal to `0`. | ||||
| 	@field LEFT | ||||
| 	*/ | ||||
| 	{ "LEFT",          GFX_LEFT   }, | ||||
| 	{ "LEFT",          GFX_LEFT           }, | ||||
| 	/***
 | ||||
| 	Constant used to select the right eye. | ||||
| 	It is equal to `1`. | ||||
| 	@field RIGHT | ||||
| 	*/ | ||||
| 	{ "RIGHT",         GFX_RIGHT  }, | ||||
| 	{ "RIGHT",         GFX_RIGHT          }, | ||||
| 	/***
 | ||||
| 	The top screen height, in pixels. | ||||
| 	It is equal to `240`. | ||||
| 	@field TOP_HEIGHT | ||||
| 	*/ | ||||
| 	{ "TOP_HEIGHT",    240        }, | ||||
| 	{ "TOP_HEIGHT",    240                }, | ||||
| 	/***
 | ||||
| 	The top screen width, in pixels. | ||||
| 	It is equal to `400`. | ||||
| 	@field TOP_WIDTH | ||||
| 	*/ | ||||
| 	{ "TOP_WIDTH",     400        }, | ||||
| 	{ "TOP_WIDTH",     400                }, | ||||
| 	/***
 | ||||
| 	The bottom screen height, in pixels. | ||||
| 	It is equal to `240`. | ||||
| 	@field BOTTOM_HEIGHT | ||||
| 	*/ | ||||
| 	{ "BOTTOM_HEIGHT", 240        }, | ||||
| 	{ "BOTTOM_HEIGHT", 240                }, | ||||
| 	/***
 | ||||
| 	The bottom screen width, in pixels. | ||||
| 	It is equal to `320`. | ||||
| 	@field BOTTOM_WIDTH | ||||
| 	*/ | ||||
| 	{ "BOTTOM_WIDTH",  320        }, | ||||
| 	{ "BOTTOM_WIDTH",  320                }, | ||||
| 	/***
 | ||||
| 	Represents a vertical gradient drawn from the top (first color) to the bottom (second color). | ||||
| 	@field TOP_TO_BOTTOM | ||||
| 	*/ | ||||
| 	{ "TOP_TO_BOTTOM", SF2D_TOP_TO_BOTTOM }, | ||||
| 	/***
 | ||||
| 	Represents a horizontal gradient drawn from the left (first color) to the right (second color). | ||||
| 	@field LEFT_TO_RIGHT | ||||
| 	*/ | ||||
| 	{ "LEFT_TO_RIGHT", SF2D_LEFT_TO_RIGHT }, | ||||
| 	{ NULL, 0 } | ||||
| }; | ||||
| 
 | ||||
|  | @ -440,6 +853,11 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); } | |||
| }; | ||||
| 
 | ||||
| int luaopen_gfx_lib(lua_State *L) { | ||||
| 	luaL_newmetatable(L, "LTarget"); | ||||
| 	lua_pushvalue(L, -1); | ||||
| 	lua_setfield(L, -2, "__index"); | ||||
| 	luaL_setfuncs(L, target_methods, 0); | ||||
| 	 | ||||
| 	luaL_newlib(L, gfx_lib); | ||||
| 	 | ||||
| 	for (int i = 0; gfx_constants[i].name; i++) { | ||||
|  |  | |||
							
								
								
									
										12
									
								
								source/gfx.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										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,15 +52,38 @@ int main() { | |||
| 	// Load libs
 | ||||
| 	luaL_openlibs(L); | ||||
| 	load_ctr_lib(L); | ||||
| 
 | ||||
| 	isGfxInitialized = true; | ||||
| 	 | ||||
| 	// Parse arguments
 | ||||
| 	for (int i=0;i<argc;i++) { | ||||
| 		if (argv[i][0] == '-') { | ||||
| 			switch(argv[i][1]) { | ||||
| 				case 'm': { // main file replacement
 | ||||
| 					mainFile = &argv[i][2]; | ||||
| 					if (argv[i][2] == ' ') mainFile = &argv[i][3]; | ||||
| 					break; | ||||
| 				} | ||||
| 				 | ||||
| 				case 'r': { // root directory replacement
 | ||||
| 					char* root; | ||||
| 					root = &argv[i][2]; | ||||
| 					if (argv[i][2] == ' ') root = &argv[i][3]; | ||||
| 					if (chdir(root)) error("No such root path"); | ||||
| 					break; | ||||
| 				} | ||||
| 					 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Do the actual thing
 | ||||
| 	if (luaL_dofile(L, "main.lua")) error(luaL_checkstring(L, -1)); | ||||
| 	if (luaL_dofile(L, mainFile)) error(luaL_checkstring(L, -1)); | ||||
| 
 | ||||
| 	// Unload libs
 | ||||
| 	unload_ctr_lib(L); | ||||
| 
 | ||||
| 	 | ||||
| 	// Unload Lua
 | ||||
| 	lua_close(L); | ||||
| 
 | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										79
									
								
								source/map.c
									
										
									
									
									
								
							
							
						
						
									
										79
									
								
								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"); | ||||
|  | @ -57,7 +61,16 @@ static int map_load(lua_State *L) { | |||
| 	map_userdata *map = lua_newuserdata(L, sizeof(map_userdata)); | ||||
| 	luaL_getmetatable(L, "LMap"); | ||||
| 	lua_setmetatable(L, -2); | ||||
| 	 | ||||
| 
 | ||||
| 	// Block GC of the texture by keeping a reference to it in the registry
 | ||||
| 	// registry[map_userdata] = texture_userdata
 | ||||
| 	lua_pushnil(L); | ||||
| 	lua_copy(L, -2, -1); // map_userdata
 | ||||
| 	lua_pushnil(L); | ||||
| 	lua_copy(L, 2, -1); // texture_userdata
 | ||||
| 	lua_settable(L, LUA_REGISTRYINDEX); | ||||
| 
 | ||||
| 	// Init userdata fields
 | ||||
| 	map->texture = texture; | ||||
| 	map->tileSizeX = tileSizeX; | ||||
| 	map->tileSizeY = tileSizeY; | ||||
|  | @ -156,50 +169,81 @@ Map object | |||
| */ | ||||
| 
 | ||||
| /***
 | ||||
| Draw a map. | ||||
| Draw (a part of) the map on the screen. | ||||
| @function :draw | ||||
| @tparam number x X position | ||||
| @tparam number y Y position | ||||
| @within Methods | ||||
| @tparam integer x X top-left coordinate to draw the map on the screen (pixels) | ||||
| @tparam integer y Y top-left coordinate to draw the map on the screen (pixels) | ||||
| @tparam[opt=0] integer offsetX drawn area X start coordinate on the map (pixels) (x=0,y=0 correspond to the first tile top-left corner) | ||||
| @tparam[opt=0] integer offsetY drawn area Y start coordinate on the map (pixels) | ||||
| @tparam[opt=400] integer width width of the drawn area on the map (pixels) | ||||
| @tparam[opt=240] integer height height of the drawn area on the map (pixels) | ||||
| @usage | ||||
| -- This will draw on the screen at x=5,y=5 a part of the map. The part is the rectangle on the map starting at x=16,y=16 and width=32,height=48. | ||||
| -- For example, if you use 16x16 pixel tiles, this will draw the tiles from 1,1 (top-left corner of the rectangle) to 2,3 (bottom-right corner). | ||||
| map:draw(5, 5, 16, 16, 32, 48) | ||||
| */ | ||||
| static int map_draw(lua_State *L) { | ||||
| 	map_userdata *map = luaL_checkudata(L, 1, "LMap"); | ||||
| 	int x = luaL_checkinteger(L, 2); | ||||
| 	int y = luaL_checkinteger(L, 3); | ||||
| 	int offsetX = luaL_optinteger(L, 4, 0); | ||||
| 	int offsetY = luaL_optinteger(L, 5, 0); | ||||
| 	int width = luaL_optinteger(L, 6, 400); | ||||
| 	int height = luaL_optinteger(L, 7, 240); | ||||
| 
 | ||||
| 	int xI = fmax(floor((double)offsetX / map->tileSizeX), 0); // initial tile X
 | ||||
| 	int xF = fmin(ceil((double)(offsetX + width) / map->tileSizeX), map->width); // final tile X
 | ||||
| 
 | ||||
| 	int yI = fmax(floor((double)offsetY / map->tileSizeY), 0); // initial tile Y
 | ||||
| 	int yF = fmin(ceil((double)(offsetY + height) / map->tileSizeY), map->height); // final tile Y
 | ||||
| 
 | ||||
| 	if (sf2d_get_current_screen() == GFX_TOP) | ||||
| 		sf2d_set_scissor_test(GPU_SCISSOR_NORMAL, x, y, fmin(width, 400 - x), fmin(height, 240 - y)); // Scissor test doesn't work when x/y + width > screenWidth/Height
 | ||||
| 	else | ||||
| 		sf2d_set_scissor_test(GPU_SCISSOR_NORMAL, x, y, fmin(width, 320 - x), fmin(height, 240 - y)); | ||||
| 
 | ||||
| 	int texX = 0; | ||||
| 	int texY = 0; | ||||
| 	 | ||||
| 
 | ||||
| 	if (map->texture->blendColor == 0xffffffff) { | ||||
| 		for (int xp=0; xp<map->width; xp++) { | ||||
| 			for (int yp=0; yp<map->height; yp++) { | ||||
| 		for (int xp = xI; xp < xF; xp++) { | ||||
| 			for (int yp = yI; yp < yF; yp++) { | ||||
| 				u16 tile = getTile(map, xp, yp); | ||||
| 				getTilePos(map, tile, &texX, &texY); | ||||
| 				sf2d_draw_texture_part(map->texture->texture, (x+(map->tileSizeX*xp)+(xp*map->spaceX)), (y+(map->tileSizeY*yp)+(yp*map->spaceY)), texX, texY, map->tileSizeX, map->tileSizeY); | ||||
| 				sf2d_draw_texture_part(map->texture->texture, x+(map->tileSizeX*xp)+(xp*map->spaceX)-offsetX, y+(map->tileSizeY*yp)+(yp*map->spaceY)-offsetY, texX, texY, map->tileSizeX, map->tileSizeY); | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		for (int xp=0; xp<map->width; xp++) { | ||||
| 			for (int yp=0; yp<map->height; yp++) { | ||||
| 		for (int xp = xI; xp < xF; xp++) { | ||||
| 			for (int yp = yI; yp < yF; yp++) { | ||||
| 				u16 tile = getTile(map, xp, yp); | ||||
| 				getTilePos(map, tile, &texX, &texY); | ||||
| 				sf2d_draw_texture_part_blend(map->texture->texture, (x+(map->tileSizeX*xp)+(xp*map->spaceX)), (y+(map->tileSizeY*yp)+(yp*map->spaceY)), texX, texY, map->tileSizeX, map->tileSizeY, map->texture->blendColor); | ||||
| 				sf2d_draw_texture_part_blend(map->texture->texture, x+(map->tileSizeX*xp)+(xp*map->spaceX)-offsetX, y+(map->tileSizeY*yp)+(yp*map->spaceY)-offsetY, texX, texY, map->tileSizeX, map->tileSizeY, map->texture->blendColor); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	sf2d_set_scissor_test(lua_scissor.mode, lua_scissor.x, lua_scissor.y, lua_scissor.width, lua_scissor.height); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Unload a map. | ||||
| @function :unload | ||||
| @within Methods | ||||
| */ | ||||
| static int map_unload(lua_State *L) { | ||||
| 	map_userdata *map = luaL_checkudata(L, 1, "LMap"); | ||||
| 
 | ||||
| 	free(map->data); | ||||
| 
 | ||||
| 	// Remove the reference to the texture in the registry
 | ||||
| 	// registry[map_userdata] = nil
 | ||||
| 	lua_pushnil(L); | ||||
| 	lua_copy(L, 1, -1); // map_userdata
 | ||||
| 	lua_pushnil(L); | ||||
| 	lua_settable(L, LUA_REGISTRYINDEX); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -208,7 +252,6 @@ Return the size of a map. | |||
| @function :getSize | ||||
| @treturn number width of the map, in tiles | ||||
| @treturn number height of the map, in tiles | ||||
| @within Methods | ||||
| */ | ||||
| static int map_getSize(lua_State *L) { | ||||
| 	map_userdata *map = luaL_checkudata(L, 1, "LMap"); | ||||
|  | @ -225,7 +268,6 @@ Return the value of a tile. | |||
| @tparam number x X position of the tile (in tiles) | ||||
| @tparam number y Y position of the tile (in tiles) | ||||
| @treturn number value of the tile | ||||
| @within Methods | ||||
| */ | ||||
| static int map_getTile(lua_State *L) { | ||||
| 	map_userdata *map = luaL_checkudata(L, 1, "LMap"); | ||||
|  | @ -243,7 +285,6 @@ Set the value of a tile. | |||
| @tparam number x X position of the tile (in tiles) | ||||
| @tparam number y Y position of the tile (in tiles) | ||||
| @tparam number value new value for the tile | ||||
| @within Methods | ||||
| */ | ||||
| static int map_setTile(lua_State *L) { | ||||
| 	map_userdata *map = luaL_checkudata(L, 1, "LMap"); | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
							
								
								
									
										378
									
								
								source/socket.c
									
										
									
									
									
								
							
							
						
						
									
										378
									
								
								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; | ||||
| 		} | ||||
| 		 | ||||
| 		u32* mem = (u32*)memalign(0x1000, size); | ||||
| 		if (mem == NULL) { | ||||
| 			lua_pushboolean(L, false); | ||||
| 			lua_pushstring(L, "Failed to allocate memory"); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		 | ||||
| 		Result ret = socInit(mem, size); | ||||
| 	 | ||||
| 	if (ret) { | ||||
| 		lua_pushboolean(L, false); | ||||
| 		lua_pushinteger(L, ret); | ||||
| 		return 2; | ||||
| 		if (R_FAILED(ret)) { | ||||
| 			lua_pushboolean(L, false); | ||||
| 			lua_pushinteger(L, ret); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		 | ||||
| 		ret = sslcInit(0); | ||||
| 		if (R_FAILED(ret)) { | ||||
| 			lua_pushboolean(L, false); | ||||
| 			lua_pushinteger(L, ret); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		 | ||||
| 		sslcCreateRootCertChain(&rootCertChain); | ||||
| 		sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_CyberTrust, NULL); | ||||
| 		sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_AddTrust_External_CA, NULL); | ||||
| 		sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_COMODO, NULL); | ||||
| 		sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_USERTrust, NULL); | ||||
| 		sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_DigiCert_EV, NULL); | ||||
| 		 | ||||
| 		initStateSocket = true; | ||||
| 	} | ||||
| 	 | ||||
| 	lua_pushboolean(L, true); | ||||
|  | @ -48,11 +92,16 @@ static int socket_init(lua_State *L) { | |||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Disable the socket module. Must be called before exiting ctrµLua. | ||||
| Disable the socket module. | ||||
| @function shutdown | ||||
| */ | ||||
| static int socket_shutdown(lua_State *L) { | ||||
| 	socExit(); | ||||
| 	if (initStateSocket) { | ||||
| 		sslcDestroyRootCertChain(rootCertChain); | ||||
| 		sslcExit(); | ||||
| 		socExit(); | ||||
| 		initStateSocket = false; | ||||
| 	} | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -60,7 +109,9 @@ static int socket_shutdown(lua_State *L) { | |||
| /***
 | ||||
| Return a TCP socket. | ||||
| @function tcp | ||||
| @treturn TCPMaster TCP socket | ||||
| @treturn[1] TCPMaster TCP socket | ||||
| @treturn[2] nil in case of error | ||||
| @treturn[2] string error message | ||||
| */ | ||||
| static int socket_tcp(lua_State *L) { | ||||
| 	socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata)); | ||||
|  | @ -76,13 +127,18 @@ static int socket_tcp(lua_State *L) { | |||
| 	 | ||||
| 	userdata->addr.sin_family = AF_INET; | ||||
| 	 | ||||
| 	userdata->isSSL = false; | ||||
| 	fcntl(userdata->socket, F_SETFL, fcntl(userdata->socket, F_GETFL, 0)|O_NONBLOCK); | ||||
| 	 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Return an UDP socket. | ||||
| @function udp | ||||
| @treturn UDPMaster UDP socket | ||||
| @treturn[1] UDPMaster UDP socket | ||||
| @treturn[2] nil in case of error | ||||
| @treturn[2] string error message | ||||
| */ | ||||
| static int socket_udp(lua_State *L) { | ||||
| 	socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata)); | ||||
|  | @ -92,15 +148,39 @@ static int socket_udp(lua_State *L) { | |||
| 	userdata->socket = socket(AF_INET, SOCK_DGRAM, 0); | ||||
| 	if (userdata->socket < 0) { | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, "Failed to create a TCP socket"); | ||||
| 		lua_pushstring(L, strerror(errno)); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	 | ||||
| 	userdata->addr.sin_family = AF_INET; | ||||
| 	fcntl(userdata->socket, F_SETFL, fcntl(userdata->socket, F_GETFL, 0)|O_NONBLOCK); | ||||
| 	 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Add a trusted root CA to the certChain. | ||||
| @function addTrustedRootCA | ||||
| @tparam string cert DER cert | ||||
| @treturn[1] boolean `true` if everything went fine | ||||
| @treturn[2] nil in case of error | ||||
| @treturn[2] number error code | ||||
| */ | ||||
| static int socket_addTrustedRootCA(lua_State *L) { | ||||
| 	size_t size = 0; | ||||
| 	const char* cert = luaL_checklstring(L, 1, &size); | ||||
| 	 | ||||
| 	Result ret = sslcAddTrustedRootCA(rootCertChain, (u8*)cert, size, NULL); | ||||
| 	if (R_FAILED(ret)) { | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushinteger(L, ret); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	 | ||||
| 	lua_pushboolean(L, true); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| All sockets | ||||
| @section sockets | ||||
|  | @ -115,7 +195,7 @@ static int socket_bind(lua_State *L) { | |||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	int port = luaL_checkinteger(L, 2); | ||||
| 	 | ||||
| 	userdata->addr.sin_addr.s_addr = htonl(INADDR_ANY); | ||||
| 	userdata->addr.sin_addr.s_addr = gethostid(); | ||||
| 	userdata->addr.sin_port = htons(port); | ||||
| 	 | ||||
| 	bind(userdata->socket, (struct sockaddr*)&userdata->addr, sizeof(userdata->addr)); | ||||
|  | @ -130,11 +210,73 @@ Close an existing socket. | |||
| static int socket_close(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	 | ||||
| 	if (userdata->isSSL) { | ||||
| 		sslcDestroyContext(&userdata->sslContext); | ||||
| 	} | ||||
| 	 | ||||
| 	closesocket(userdata->socket); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Get some informations from a socket. | ||||
| @function :getpeername | ||||
| @treturn string IP | ||||
| @treturn number port | ||||
| */ | ||||
| static int socket_getpeername(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	 | ||||
| 	struct sockaddr_in addr; | ||||
| 	socklen_t addrSize = sizeof(addr); | ||||
| 	 | ||||
| 	getpeername(userdata->socket, (struct sockaddr*)&addr, &addrSize); | ||||
| 	 | ||||
| 	lua_pushstring(L, inet_ntoa(addr.sin_addr)); | ||||
| 	lua_pushinteger(L, ntohs(addr.sin_port)); | ||||
| 	 | ||||
| 	return 2; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Get some local informations from a socket. | ||||
| @function :getsockname | ||||
| @treturn string IP | ||||
| @treturn number port | ||||
| */ | ||||
| static int socket_getsockname(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	 | ||||
| 	struct sockaddr_in addr; | ||||
| 	socklen_t addrSize = sizeof(addr); | ||||
| 	 | ||||
| 	getsockname(userdata->socket, (struct sockaddr*)&addr, &addrSize); | ||||
| 	 | ||||
| 	lua_pushstring(L, inet_ntoa(addr.sin_addr)); | ||||
| 	lua_pushinteger(L, ntohs(addr.sin_port)); | ||||
| 	 | ||||
| 	return 2; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Set if the socket should be blocking. | ||||
| @function :setBlocking | ||||
| @tparam[opt=true] boolean block if `false`, the socket won't block | ||||
| */ | ||||
| static int socket_setBlocking(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	bool block = true; | ||||
| 	if (lua_isboolean(L, 2)) | ||||
| 		block = lua_toboolean(L, 2); | ||||
| 	 | ||||
| 	int flags = fcntl(userdata->socket, F_GETFL, 0); | ||||
| 	flags = block?(flags&~O_NONBLOCK):(flags|O_NONBLOCK); | ||||
| 	fcntl(userdata->socket, F_SETFL, flags); | ||||
| 	 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| TCP Sockets | ||||
| @section TCP | ||||
|  | @ -151,6 +293,7 @@ static int socket_accept(lua_State *L) { | |||
| 	socket_userdata *client = lua_newuserdata(L, sizeof(*client)); | ||||
| 	luaL_getmetatable(L, "LSocket"); | ||||
| 	lua_setmetatable(L, -2); | ||||
| 	client->isSSL = false; | ||||
| 	 | ||||
| 	socklen_t addrSize = sizeof(client->addr); | ||||
| 	client->socket = accept(userdata->socket, (struct sockaddr*)&client->addr, &addrSize); | ||||
|  | @ -167,6 +310,7 @@ Connect a socket to a server. The TCP object becomes a TCPClient object. | |||
| @function :connect | ||||
| @tparam string host address of the host | ||||
| @tparam number port port of the server | ||||
| @tparam[opt=false] boolean ssl use SSL if `true` | ||||
| @treturn[1] boolean true if success | ||||
| @treturn[2] boolean false if failed | ||||
| @treturn[2] string error string | ||||
|  | @ -175,11 +319,12 @@ static int socket_connect(lua_State *L) { | |||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	char *addr = (char*)luaL_checkstring(L, 2); | ||||
| 	int port = luaL_checkinteger(L, 3); | ||||
| 	bool ssl = lua_toboolean(L, 4); | ||||
| 	 | ||||
| 	userdata->host = gethostbyname(addr); | ||||
| 	if (userdata->host == NULL) { | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, "No such host"); | ||||
| 		lua_pushstring(L, strerror(errno)); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	 | ||||
|  | @ -188,10 +333,22 @@ static int socket_connect(lua_State *L) { | |||
| 	 | ||||
| 	if (connect(userdata->socket, (const struct sockaddr*)&userdata->addr, sizeof(userdata->addr)) < 0) { | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, "Connection failed"); | ||||
| 		lua_pushstring(L, strerror(errno)); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	 | ||||
| 	if (ssl) { // SSL context setup
 | ||||
| 		sslcCreateContext(&userdata->sslContext, userdata->socket, SSLCOPT_Default, addr); | ||||
| 		sslcContextSetRootCertChain(&userdata->sslContext, rootCertChain); | ||||
| 		if (R_FAILED(sslcStartConnection(&userdata->sslContext, NULL, NULL))) { | ||||
| 			sslcDestroyContext(&userdata->sslContext); | ||||
| 			lua_pushnil(L); | ||||
| 			lua_pushstring(L, "SSL connection failed"); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		userdata->isSSL = true; | ||||
| 	} | ||||
| 	 | ||||
| 	lua_pushboolean(L, 1); | ||||
| 	return 1; | ||||
| } | ||||
|  | @ -218,7 +375,9 @@ If no data is avaible, it returns an empty string (non-blocking). | |||
| 							   "a" to receive everything, | ||||
| 							   "l" to receive the next line, skipping the end of line, | ||||
| 							   "L" to receive the next line, keeping the end of line. | ||||
| @treturn string data | ||||
| @treturn[1] string data | ||||
| @treturn[2] nil in case of error | ||||
| @treturn[2] integer error code | ||||
| */ | ||||
| static int socket_receive(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
|  | @ -237,7 +396,11 @@ static int socket_receive(lua_State *L) { | |||
| 			luaL_buffinit(L, &b); | ||||
| 
 | ||||
| 			char buff; | ||||
| 			while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff); | ||||
| 			if (!userdata->isSSL) { | ||||
| 				while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff); | ||||
| 			} else { | ||||
| 				while (!R_FAILED(sslcRead(&userdata->sslContext, &buff, 1, false)) && buff != '\n') luaL_addchar(&b, buff); | ||||
| 			} | ||||
| 
 | ||||
| 			luaL_pushresult(&b); | ||||
| 			return 1; | ||||
|  | @ -247,7 +410,11 @@ static int socket_receive(lua_State *L) { | |||
| 			luaL_buffinit(L, &b); | ||||
| 
 | ||||
| 			char buff; | ||||
| 			while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff); | ||||
| 			if (!userdata->isSSL) { | ||||
| 				while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff); | ||||
| 			} else { | ||||
| 				while (buff != '\n' && !R_FAILED(sslcRead(&userdata->sslContext, &buff, 1, false))) luaL_addchar(&b, buff); | ||||
| 			} | ||||
| 
 | ||||
| 			luaL_pushresult(&b); | ||||
| 			return 1; | ||||
|  | @ -258,7 +425,17 @@ static int socket_receive(lua_State *L) { | |||
| 	} | ||||
| 	 | ||||
| 	char *buff = malloc(count+1); | ||||
| 	int len = recv(userdata->socket, buff, count, flags); | ||||
| 	int len; | ||||
| 	if (!userdata->isSSL) { | ||||
| 		len = recv(userdata->socket, buff, count, flags); | ||||
| 	} else { | ||||
| 		len = sslcRead(&userdata->sslContext, buff, count, false); | ||||
| 		if (R_FAILED(len)) { | ||||
| 			lua_pushnil(L); | ||||
| 			lua_pushinteger(L, len); | ||||
| 			return 2; | ||||
| 		} | ||||
| 	} | ||||
| 	*(buff+len) = 0x0; // text end
 | ||||
| 	 | ||||
| 	lua_pushstring(L, buff); | ||||
|  | @ -269,14 +446,32 @@ static int socket_receive(lua_State *L) { | |||
| Send some data over the TCP socket. | ||||
| @function :send | ||||
| @tparam string data data to send | ||||
| @treturn number amount of data sent | ||||
| @treturn[1] number amount of data sent | ||||
| @treturn[2] nil in case of error | ||||
| @treturn[2] integer/string error code/message | ||||
| */ | ||||
| static int socket_send(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	size_t size = 0; | ||||
| 	char *data = (char*)luaL_checklstring(L, 2, &size); | ||||
| 	 | ||||
| 	size_t sent = send(userdata->socket, data, size, 0); | ||||
| 	ssize_t sent; | ||||
| 	if (!userdata->isSSL) { | ||||
| 		sent = send(userdata->socket, data, size, 0); | ||||
| 	} else { | ||||
| 		sent = sslcWrite(&userdata->sslContext, data, size); | ||||
| 		if (R_FAILED(sent)) { | ||||
| 			lua_pushnil(L); | ||||
| 			lua_pushinteger(L, sent); | ||||
| 			return 2; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	if (sent < 0) { | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, strerror(errno)); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	 | ||||
| 	lua_pushinteger(L, sent); | ||||
| 	return 1; | ||||
|  | @ -288,97 +483,114 @@ UDP sockets | |||
| */ | ||||
| 
 | ||||
| /***
 | ||||
| Receive some data from a server. | ||||
| Receive a datagram from the UDP object. | ||||
| @function :receivefrom | ||||
| @tparam number count amount of data to receive | ||||
| @tparam string host host name | ||||
| @tparam number port port | ||||
| @treturn string data | ||||
| @tparam[opt=8191] number count maximum amount of bytes to receive from the datagram. Must be lower than 8192. | ||||
| @treturn[1] string data | ||||
| @treturn[1] string IP address of the sender | ||||
| @treturn[1] integer port number of the sender | ||||
| @treturn[2] nil in case of error or no datagram to receive | ||||
| @treturn[2] string error message | ||||
| */ | ||||
| static int socket_receivefrom(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	int count = luaL_checkinteger(L, 2); | ||||
| 	size_t namesize = 0; | ||||
| 	char *hostname = (char*)luaL_optlstring(L, 3, NULL, &namesize); | ||||
| 	int port = luaL_optinteger(L, 4, 0); | ||||
| 	 | ||||
| 	struct sockaddr_in from = {0}; | ||||
| 	if (hostname != NULL) { // For a server
 | ||||
| 		struct hostent *hostinfo = gethostbyname(hostname); | ||||
| 		if (hostinfo == NULL) { | ||||
| 			lua_pushnil(L); | ||||
| 			return 1; | ||||
| 		} | ||||
| 		from.sin_addr = *(struct in_addr*)hostinfo->h_addr; | ||||
| 		from.sin_port = htons(port); | ||||
| 		from.sin_family = AF_INET; | ||||
| 	int count = luaL_optinteger(L, 2, 8191); | ||||
| 
 | ||||
| 	struct sockaddr_in from; | ||||
| 	socklen_t addr_len; | ||||
| 
 | ||||
| 	char* buffer = calloc(1, count+1); | ||||
| 	ssize_t n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr *)&from, &addr_len); | ||||
| 
 | ||||
| 	if (n == 0) { | ||||
| 		free(buffer); | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, "nothing to receive"); | ||||
| 		return 2; | ||||
| 
 | ||||
| 	} else if (n < 0) { | ||||
| 		free(buffer); | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, strerror(n)); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	 | ||||
| 	char* buffer = malloc(count+1); | ||||
| 	int n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr*)&from, NULL); | ||||
| 	*(buffer+n) = 0x0; | ||||
| 	 | ||||
| 	lua_pushstring(L, buffer); | ||||
| 	if (hostname != NULL) { | ||||
| 		return 1; | ||||
| 	} else { | ||||
| 		lua_pushstring(L, inet_ntoa(from.sin_addr)); | ||||
| 		lua_pushinteger(L, ntohs(from.sin_port)); | ||||
| 		return 3; | ||||
| 	} | ||||
| 	lua_pushstring(L, inet_ntoa(from.sin_addr)); | ||||
| 	lua_pushinteger(L, ntohs(from.sin_port)); | ||||
| 
 | ||||
| 	free(buffer); | ||||
| 
 | ||||
| 	return 3; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Send some data to a server. | ||||
| Send a datagram to the specified IP and port. | ||||
| @function :sendto | ||||
| @tparam string data data to send | ||||
| @tparam string host host name | ||||
| @tparam number port port | ||||
| @tparam string host IP/hostname of the recipient | ||||
| @tparam number port port number of the recipient | ||||
| @treturn[1] boolean true in case of success | ||||
| @treturn[2] boolean false in case of error | ||||
| @treturn[2] string error message | ||||
| */ | ||||
| static int socket_sendto(lua_State *L) { | ||||
| 	socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); | ||||
| 	size_t datasize = 0; | ||||
| 	char *data = (char*)luaL_checklstring(L, 2, &datasize); | ||||
| 	size_t namesize = 0; | ||||
| 	char *hostname = (char*)luaL_checklstring(L, 3, &namesize); | ||||
| 	size_t datasize; | ||||
| 	const char *data = luaL_checklstring(L, 2, &datasize); | ||||
| 	const char *hostname = luaL_checkstring(L, 3); | ||||
| 	int port = luaL_checkinteger(L, 4); | ||||
| 	 | ||||
| 
 | ||||
| 	struct hostent *hostinfo = gethostbyname(hostname); | ||||
| 	if (hostinfo == NULL) { | ||||
| 		lua_pushnil(L); | ||||
| 		return 1; | ||||
| 		lua_pushboolean(L, false); | ||||
| 		lua_pushstring(L, "unknown host"); | ||||
| 		return 2; | ||||
| 	} | ||||
| 	struct sockaddr_in to = {0}; | ||||
| 	to.sin_addr = *(struct in_addr*)hostinfo->h_addr; | ||||
| 
 | ||||
| 	struct sockaddr_in to; | ||||
| 	to.sin_addr = *(struct in_addr *)hostinfo->h_addr; | ||||
| 	to.sin_port = htons(port); | ||||
| 	to.sin_family = AF_INET; | ||||
| 	 | ||||
| 	sendto(userdata->socket, data, datasize, 0, (struct sockaddr*)&to, sizeof(to)); | ||||
| 	 | ||||
| 	return 0; | ||||
| 
 | ||||
| 	ssize_t n = sendto(userdata->socket, data, datasize, 0, (struct sockaddr *)&to, sizeof(to)); | ||||
| 
 | ||||
| 	if (n < 0) { | ||||
| 		lua_pushboolean(L, false); | ||||
| 		lua_pushstring(L, strerror(n)); | ||||
| 		return 2; | ||||
| 	} | ||||
| 
 | ||||
| 	lua_pushboolean(L, true); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| // module functions
 | ||||
| // Module functions
 | ||||
| static const struct luaL_Reg socket_functions[] = { | ||||
| 	{"init",     socket_init    }, | ||||
| 	{"shutdown", socket_shutdown}, | ||||
| 	{"tcp",      socket_tcp     }, | ||||
| 	{"udp",      socket_udp     }, | ||||
| 	{ "init",             socket_init             }, | ||||
| 	{ "shutdown",         socket_shutdown         }, | ||||
| 	{ "tcp",              socket_tcp              }, | ||||
| 	{ "udp",              socket_udp              }, | ||||
| 	{ "addTrustedRootCA", socket_addTrustedRootCA }, | ||||
| 	{NULL, NULL} | ||||
| }; | ||||
| 
 | ||||
| // object
 | ||||
| // Object methods
 | ||||
| static const struct luaL_Reg socket_methods[] = { | ||||
| 	{"accept",      socket_accept     }, | ||||
| 	{"bind",        socket_bind       }, | ||||
| 	{"close",       socket_close      }, | ||||
| 	{"__gc",        socket_close      }, | ||||
| 	{"connect",     socket_connect    }, | ||||
| 	{"listen",      socket_listen     }, | ||||
| 	{"receive",     socket_receive    }, | ||||
| 	{"receivefrom", socket_receivefrom}, | ||||
| 	{"send",        socket_send       }, | ||||
| 	{"sendto",      socket_sendto     }, | ||||
| 	{ "accept",      socket_accept      }, | ||||
| 	{ "bind",        socket_bind        }, | ||||
| 	{ "close",       socket_close       }, | ||||
| 	{ "setBlocking", socket_setBlocking }, | ||||
| 	{ "__gc",        socket_close       }, | ||||
| 	{ "connect",     socket_connect     }, | ||||
| 	{ "listen",      socket_listen      }, | ||||
| 	{ "receive",     socket_receive     }, | ||||
| 	{ "receivefrom", socket_receivefrom }, | ||||
| 	{ "send",        socket_send        }, | ||||
| 	{ "sendto",      socket_sendto      }, | ||||
| 	{ "getpeername", socket_getpeername }, | ||||
| 	{ "getsockname", socket_getsockname }, | ||||
| 	{NULL, NULL} | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										208
									
								
								source/texture.c
									
										
									
									
									
								
							
							
						
						
									
										208
									
								
								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); | ||||
|  | @ -46,10 +54,10 @@ static int texture_load(lua_State *L) { | |||
| 
 | ||||
| 	texture_userdata *texture; | ||||
| 	texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture)); | ||||
| 	 | ||||
| 
 | ||||
| 	luaL_getmetatable(L, "LTexture"); | ||||
| 	lua_setmetatable(L, -2); | ||||
| 	 | ||||
| 
 | ||||
| 	if (type==3) type = getType(path); | ||||
| 	if (type==0) { //PNG
 | ||||
| 		texture->texture = sfil_load_PNG_file(path, place); | ||||
|  | @ -58,21 +66,27 @@ static int texture_load(lua_State *L) { | |||
| 	} else if (type==2) { //BMP
 | ||||
| 		texture->texture = sfil_load_BMP_file(path, place); //appears to be broken right now.
 | ||||
| 	} else { | ||||
| 		lua_pushnil(L); | ||||
| 		lua_pushstring(L, "Bad type"); | ||||
| 		return 2; | ||||
| 		int w, h; | ||||
| 		char* data = (char*)stbi_load(path, &w, &h, NULL, 4); | ||||
| 		if (data == NULL) { | ||||
| 			lua_pushnil(L); | ||||
| 			lua_pushstring(L, "Can't open file"); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		texture->texture = sf2d_create_texture_mem_RGBA8(data, w, h, TEXFMT_RGBA8, place); | ||||
| 		free(data); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	if (texture->texture == NULL) { | ||||
| 	  lua_pushnil(L); | ||||
| 	  lua_pushstring(L, "No such file"); | ||||
| 	  return 2; | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	texture->scaleX = 1.0f; | ||||
| 	texture->scaleY = 1.0f; | ||||
| 	texture->blendColor = 0xffffffff; | ||||
| 	 | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -88,20 +102,20 @@ static int texture_new(lua_State *L) { | |||
| 	int w = luaL_checkinteger(L, 1); | ||||
| 	int h = luaL_checkinteger(L, 2); | ||||
| 	u8 place = luaL_checkinteger(L, 3); | ||||
| 	 | ||||
| 
 | ||||
| 	texture_userdata *texture; | ||||
| 	texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture)); | ||||
| 	 | ||||
| 
 | ||||
| 	luaL_getmetatable(L, "LTexture"); | ||||
| 	lua_setmetatable(L, -2); | ||||
| 	 | ||||
| 
 | ||||
| 	texture->texture = sf2d_create_texture(w, h, TEXFMT_RGBA8, place); | ||||
| 	sf2d_texture_tile32(texture->texture); | ||||
| 	 | ||||
| 
 | ||||
| 	texture->scaleX = 1.0f; | ||||
| 	texture->scaleY = 1.0f; | ||||
| 	texture->blendColor = 0xffffffff; | ||||
| 	 | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -113,35 +127,41 @@ Texture object | |||
| /***
 | ||||
| Draw a texture. | ||||
| @function :draw | ||||
| @tparam number x X position | ||||
| @tparam number y Y position | ||||
| @tparam[opt=0.0] number rad rotation of the texture (in radians) | ||||
| @tparam integer x X position | ||||
| @tparam integer y Y position | ||||
| @tparam[opt=0.0] number rad rotation of the texture around the hotspot (in radians) | ||||
| @tparam[opt=0.0] number hotspotX the hostpot X coordinate | ||||
| @tparam[opt=0.0] number hotspotY the hostpot Y coordinate | ||||
| */ | ||||
| static int texture_draw(lua_State *L) { | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	int x = luaL_checkinteger(L, 2); | ||||
| 	int y = luaL_checkinteger(L, 3); | ||||
| 	float rad = luaL_optnumber(L, 4, 0.0f); | ||||
| 	 | ||||
| 	float hotspotX = luaL_optnumber(L, 5, 0.0f); | ||||
| 	float hotspotY = luaL_optnumber(L, 6, 0.0f); | ||||
| 
 | ||||
| 	if (rad == 0.0f && texture->scaleX == 1.0f && texture->scaleY == 1.0f && texture->blendColor == 0xffffffff) { | ||||
| 		sf2d_draw_texture(texture->texture, x, y); | ||||
| 		sf2d_draw_texture(texture->texture, x - hotspotX, y - hotspotY); | ||||
| 	} else { | ||||
| 		sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, 0, 0, texture->texture->width, texture->texture->height, texture->scaleX, texture->scaleY, texture->blendColor); | ||||
| 		sf2d_draw_texture_rotate_scale_hotspot_blend(texture->texture, x, y, rad, texture->scaleX, texture->scaleY, hotspotX, hotspotY, texture->blendColor); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Draw a part of the texture | ||||
| @function :drawPart | ||||
| @tparam number x X position | ||||
| @tparam number y Y position | ||||
| @tparam number sx X position of the beginning of the part | ||||
| @tparam number sy Y position of the beginning of the part | ||||
| @tparam number w width of the part | ||||
| @tparam number h height of the part | ||||
| @tparam[opt=0.0] number rad rotation of the part (in radians) | ||||
| @tparam integer x X position | ||||
| @tparam integer y Y position | ||||
| @tparam integer sx X position of the beginning of the part | ||||
| @tparam integer sy Y position of the beginning of the part | ||||
| @tparam integer w width of the part | ||||
| @tparam integer h height of the part | ||||
| @tparam[opt=0.0] number rad rotation of the part around the hotspot (in radians) | ||||
| @tparam[opt=0.0] number hotspotX the hostpot X coordinate | ||||
| @tparam[opt=0.0] number hotspotY the hostpot Y coordinate | ||||
| */ | ||||
| static int texture_drawPart(lua_State *L) { | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
|  | @ -152,9 +172,11 @@ static int texture_drawPart(lua_State *L) { | |||
| 	int w = luaL_checkinteger(L, 6); | ||||
| 	int h = luaL_checkinteger(L, 7); | ||||
| 	int rad = luaL_optnumber(L, 8, 0.0f); | ||||
| 	 | ||||
| 	sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, texture->blendColor); | ||||
| 	 | ||||
| 	float hotspotX = luaL_optnumber(L, 9, 0.0f); | ||||
| 	float hotspotY = luaL_optnumber(L, 10, 0.0f); | ||||
| 
 | ||||
| 	sf2d_draw_texture_part_rotate_scale_hotspot_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, hotspotX, hotspotY, texture->blendColor); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -179,12 +201,12 @@ Unload a texture. | |||
| */ | ||||
| static int texture_unload(lua_State *L) { | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	 | ||||
| 
 | ||||
| 	if (texture->texture == NULL) return 0; | ||||
| 
 | ||||
| 	sf2d_free_texture(texture->texture); | ||||
| 	texture->texture = NULL; | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -192,16 +214,16 @@ static int texture_unload(lua_State *L) { | |||
| Rescale the texture. The default scale is `1.0`. | ||||
| @function :scale | ||||
| @tparam number scaleX new scale of the width | ||||
| @tparam number scaleY new scale of the height | ||||
| @tparam[opt=scaleX] number scaleY new scale of the height | ||||
| */ | ||||
| static int texture_scale(lua_State *L) { | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	float sx = luaL_checknumber(L, 2); | ||||
| 	float sy = luaL_checknumber(L, 3); | ||||
| 	 | ||||
| 	float sy = luaL_optnumber(L, 3, sx); | ||||
| 
 | ||||
| 	texture->scaleX = sx; | ||||
| 	texture->scaleY = sy; | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -216,9 +238,9 @@ static int texture_getPixel(lua_State *L) { | |||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	int x = luaL_checkinteger(L, 2); | ||||
| 	int y = luaL_checkinteger(L, 3); | ||||
| 	 | ||||
| 
 | ||||
| 	lua_pushinteger(L, sf2d_get_pixel(texture->texture, x, y)); | ||||
| 	 | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -234,9 +256,9 @@ static int texture_setPixel(lua_State *L) { | |||
| 	int x = luaL_checkinteger(L, 2); | ||||
| 	int y = luaL_checkinteger(L, 3); | ||||
| 	u32 color = luaL_checkinteger(L, 4); | ||||
| 	 | ||||
| 
 | ||||
| 	sf2d_set_pixel(texture->texture, x, y, color); | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -246,14 +268,106 @@ Set the blend color of the texture. | |||
| @tparam number color new blend color | ||||
| */ | ||||
| static int texture_setBlendColor(lua_State *L) { | ||||
|   texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	u32 color = luaL_checkinteger(L, 2); | ||||
| 	 | ||||
| 
 | ||||
| 	texture->blendColor = color; | ||||
| 	 | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Get the blend color of the texture. | ||||
| @function :getBlendColor | ||||
| @treturn number the blend color | ||||
| */ | ||||
| static int texture_getBlendColor(lua_State *L) { | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 
 | ||||
| 	lua_pushinteger(L, texture->blendColor); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /***
 | ||||
| Save a texture to a file. | ||||
| @function :save | ||||
| @tparam string filename path to the file to save the texture to | ||||
| @tparam[opt=TYPE_PNG] number type type of the image to save. Can be TYPE_PNG or TYPE_BMP | ||||
| @treturn[1] boolean true on success | ||||
| @treturn[2] boolean `false` in case of error | ||||
| @treturn[2] string error message | ||||
| */ | ||||
| static int texture_save(lua_State *L) { | ||||
| 	texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); | ||||
| 	const char* path = luaL_checkstring(L, 2); | ||||
| 	u8 type = luaL_optinteger(L, 3, 0); | ||||
| 
 | ||||
| 	int result = 0; | ||||
| 	if (type == 0) { // PNG
 | ||||
| 		FILE* file = fopen(path, "wb"); | ||||
| 		if (file == NULL) { | ||||
| 			lua_pushboolean(L, false); | ||||
| 			lua_pushstring(L, "Can open file"); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | ||||
| 		png_infop infos = png_create_info_struct(png); | ||||
| 		setjmp(png_jmpbuf(png)); | ||||
| 		png_init_io(png, file); | ||||
| 
 | ||||
| 		png_set_IHDR(png, infos, texture->texture->width, texture->texture->height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | ||||
| 		png_write_info(png, infos); | ||||
| 
 | ||||
| 		png_bytep row = malloc(4 * texture->texture->width * sizeof(png_byte)); | ||||
| 
 | ||||
| 		for(int y=0;y<texture->texture->height;y++) { | ||||
| 			for (int x=0;x<texture->texture->width;x++) { | ||||
| 				((u32*)row)[x] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y)); | ||||
| 			} | ||||
| 			png_write_row(png, row); | ||||
| 		} | ||||
| 
 | ||||
| 		png_write_end(png, NULL); | ||||
| 
 | ||||
| 		fclose(file); | ||||
| 		png_free_data(png, infos, PNG_FREE_ALL, -1); | ||||
| 		png_destroy_write_struct(&png, &infos); | ||||
| 		free(row); | ||||
| 
 | ||||
| 		result = 1; | ||||
| 
 | ||||
| 	} else if (type == 2) { // BMP
 | ||||
| 		u32* buff = malloc(texture->texture->width * texture->texture->height * 4); | ||||
| 		if (buff == NULL) { | ||||
| 			lua_pushboolean(L, false); | ||||
| 			lua_pushstring(L, "Failed to allocate buffer"); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		for (int y=0;y<texture->texture->height;y++) { | ||||
| 			for (int x=0;x<texture->texture->width;x++) { | ||||
| 				buff[x+(y*texture->texture->width)] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y)); | ||||
| 			} | ||||
| 		} | ||||
| 		result = stbi_write_bmp(path, texture->texture->width, texture->texture->height, 4, buff); | ||||
| 		free(buff); | ||||
| 	 | ||||
| 	} else { | ||||
| 		lua_pushboolean(L, false); | ||||
| 		lua_pushstring(L, "Not a valid type"); | ||||
| 		return 2; | ||||
| 	} | ||||
| 
 | ||||
| 	if (result == 0) { | ||||
| 		lua_pushboolean(L, false); | ||||
| 		lua_pushstring(L, "Failed to save the texture"); | ||||
| 		return 2; | ||||
| 	} | ||||
| 
 | ||||
| 	lua_pushboolean(L, true); | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| // object
 | ||||
| static const struct luaL_Reg texture_methods[] = { | ||||
| 	{ "draw",          texture_draw          }, | ||||
|  | @ -264,6 +378,8 @@ static const struct luaL_Reg texture_methods[] = { | |||
| 	{ "getPixel",      texture_getPixel      }, | ||||
| 	{ "setPixel",      texture_setPixel      }, | ||||
| 	{ "setBlendColor", texture_setBlendColor }, | ||||
| 	{ "getBlendColor", texture_getBlendColor }, | ||||
| 	{ "save",          texture_save          }, | ||||
| 	{ "__gc",          texture_unload        }, | ||||
| 	{NULL, NULL} | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										589
									
								
								source/uds.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										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