├── LUA ├── config.sun ├── modules │ ├── calc.lua │ ├── ftp.lua │ ├── record.lua │ ├── info.lua │ ├── mail.lua │ ├── camera.lua │ ├── themes.lua │ ├── gallery.lua │ ├── game.lua │ ├── music.lua │ ├── clock.lua │ ├── extdata.lua │ ├── video.lua │ ├── cia.lua │ └── fb.lua ├── scripts │ ├── title_list.lua │ ├── music_api.lua │ └── funcs.lua └── sun_index.lua ├── themes └── Default │ ├── images │ ├── 0.jpg │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── bg.jpg │ ├── cia.jpg │ ├── fb.jpg │ ├── ftp.jpg │ ├── calc.jpg │ ├── camera.jpg │ ├── charge.jpg │ ├── clock.jpg │ ├── game.jpg │ ├── info.jpg │ ├── keys.png │ ├── keys_c.png │ ├── keys_t.png │ ├── mail.jpg │ ├── music.jpg │ ├── nums.png │ ├── nums_c.png │ ├── nums_t.png │ ├── record.jpg │ ├── themes.jpg │ ├── video.jpg │ ├── extdata.jpg │ ├── ftp_icon.jpg │ ├── gallery.jpg │ ├── keys_c_t.png │ ├── nums_c_t.png │ ├── clock_icon.jpg │ └── music_icon.jpg │ ├── fonts │ └── main.ttf │ ├── sounds │ └── alarm.ogg │ └── colors.lua ├── README.md ├── .gitattributes ├── .gitignore └── LICENSE.txt /LUA/config.sun: -------------------------------------------------------------------------------- 1 | main_dir = "/3ds/Sunshell" -------------------------------------------------------------------------------- /LUA/modules/calc.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/LUA/modules/calc.lua -------------------------------------------------------------------------------- /LUA/scripts/title_list.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/LUA/scripts/title_list.lua -------------------------------------------------------------------------------- /themes/Default/images/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/0.jpg -------------------------------------------------------------------------------- /themes/Default/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/1.jpg -------------------------------------------------------------------------------- /themes/Default/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/2.jpg -------------------------------------------------------------------------------- /themes/Default/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/3.jpg -------------------------------------------------------------------------------- /themes/Default/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/4.jpg -------------------------------------------------------------------------------- /themes/Default/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/5.jpg -------------------------------------------------------------------------------- /themes/Default/fonts/main.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/fonts/main.ttf -------------------------------------------------------------------------------- /themes/Default/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/bg.jpg -------------------------------------------------------------------------------- /themes/Default/images/cia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/cia.jpg -------------------------------------------------------------------------------- /themes/Default/images/fb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/fb.jpg -------------------------------------------------------------------------------- /themes/Default/images/ftp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/ftp.jpg -------------------------------------------------------------------------------- /themes/Default/images/calc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/calc.jpg -------------------------------------------------------------------------------- /themes/Default/images/camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/camera.jpg -------------------------------------------------------------------------------- /themes/Default/images/charge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/charge.jpg -------------------------------------------------------------------------------- /themes/Default/images/clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/clock.jpg -------------------------------------------------------------------------------- /themes/Default/images/game.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/game.jpg -------------------------------------------------------------------------------- /themes/Default/images/info.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/info.jpg -------------------------------------------------------------------------------- /themes/Default/images/keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/keys.png -------------------------------------------------------------------------------- /themes/Default/images/keys_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/keys_c.png -------------------------------------------------------------------------------- /themes/Default/images/keys_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/keys_t.png -------------------------------------------------------------------------------- /themes/Default/images/mail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/mail.jpg -------------------------------------------------------------------------------- /themes/Default/images/music.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/music.jpg -------------------------------------------------------------------------------- /themes/Default/images/nums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/nums.png -------------------------------------------------------------------------------- /themes/Default/images/nums_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/nums_c.png -------------------------------------------------------------------------------- /themes/Default/images/nums_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/nums_t.png -------------------------------------------------------------------------------- /themes/Default/images/record.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/record.jpg -------------------------------------------------------------------------------- /themes/Default/images/themes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/themes.jpg -------------------------------------------------------------------------------- /themes/Default/images/video.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/video.jpg -------------------------------------------------------------------------------- /themes/Default/sounds/alarm.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/sounds/alarm.ogg -------------------------------------------------------------------------------- /themes/Default/images/extdata.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/extdata.jpg -------------------------------------------------------------------------------- /themes/Default/images/ftp_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/ftp_icon.jpg -------------------------------------------------------------------------------- /themes/Default/images/gallery.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/gallery.jpg -------------------------------------------------------------------------------- /themes/Default/images/keys_c_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/keys_c_t.png -------------------------------------------------------------------------------- /themes/Default/images/nums_c_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/nums_c_t.png -------------------------------------------------------------------------------- /themes/Default/images/clock_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/clock_icon.jpg -------------------------------------------------------------------------------- /themes/Default/images/music_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rinnegatamante/Sunshell/HEAD/themes/Default/images/music_icon.jpg -------------------------------------------------------------------------------- /themes/Default/colors.lua: -------------------------------------------------------------------------------- 1 | black = Color.new(0,0,0) 2 | white = Color.new(255,255,255) 3 | green_wifi = Color.new(0,166,81) 4 | red_wifi = Color.new(200,0,0) 5 | selected = Color.new(255,0,0) 6 | selected_item = Color.new(0,0,200,50) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the official repository of Sunshell, first multifunction shell for 3DS.

2 | NOTE: Here you'll find only LUA scripts. Scripts run under lpp-3ds so if you want to use latest commits, please grab latest commit of lpp-3ds from main repository ( https://github.com/Rinnegatamante/lpp-3ds ). -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /LUA/modules/ftp.lua: -------------------------------------------------------------------------------- 1 | -- Set private "FTP" mode 2 | mode = "FTP" 3 | 4 | -- Module background code 5 | function BackgroundFTP() 6 | last_shared = Network.updateFTP() 7 | end 8 | 9 | function FTPGC() 10 | Socket.term() 11 | last_shared = nil 12 | end 13 | 14 | -- Internal module settings 15 | FreeIconTopbar("FTP") 16 | if Network.isWifiEnabled() then 17 | if build == "Ninjhax 2" then 18 | ShowError("Network features currently unavailable on Ninjhax 2.") 19 | CallMainMenu() 20 | else 21 | if last_shared == nil then 22 | last_shared = "Waiting for connection..." 23 | Socket.init() 24 | table.insert(bg_apps,{BackgroundFTP,FTPGC,"FTP Server"}) -- Adding FTP module to background apps 25 | end 26 | end 27 | else 28 | ShowError("You need to be connected to an Hotspot to use FTP server.") 29 | CallMainMenu() 30 | end 31 | 32 | -- Rendering functions 33 | function AppTopScreenRender() 34 | Graphics.fillRect(5,395,40,220,black) 35 | Graphics.fillRect(6,394,41,219,white) 36 | end 37 | 38 | function AppBottomScreenRender() 39 | Graphics.fillRect(5,315,40,92,black) 40 | Graphics.fillRect(6,314,41,91,white) 41 | end 42 | 43 | -- Module main cycle 44 | function AppMainCycle() 45 | 46 | -- Draw FTP info 47 | Font.print(ttf,9,45,"IP: "..Network.getIPAddress(),black,TOP_SCREEN) 48 | Font.print(ttf,9,60,"Port: 5000",black,TOP_SCREEN) 49 | ftp_cmd = LinesGenerator(last_shared,90) 50 | for i,line in pairs(ftp_cmd) do 51 | Font.print(ttf,9,line[2],line[1],black,TOP_SCREEN) 52 | end 53 | 54 | -- Draw bottom screen commands info 55 | Font.print(ttf,9,45,"A = Restart FTP server",black,BOTTOM_SCREEN) 56 | Font.print(ttf,9,60,"SELECT = Return Main Menu",black,BOTTOM_SCREEN) 57 | Font.print(ttf,9,75,"B = Term FTP server",black,BOTTOM_SCREEN) 58 | 59 | -- Sets controls triggering 60 | if Controls.check(pad,KEY_A) and not Controls.check(oldpad,KEY_A) then 61 | CloseBGApp("FTP Server") 62 | dofile(main_dir.."/modules/ftp.lua") 63 | elseif Controls.check(pad,KEY_SELECT) and not Controls.check(oldpad,KEY_SELECT) then 64 | AddIconTopbar(theme_dir.."/images/ftp_icon.jpg","FTP") 65 | CallMainMenu() 66 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 67 | CloseBGApp("FTP Server") 68 | CallMainMenu() 69 | end 70 | end -------------------------------------------------------------------------------- /LUA/scripts/music_api.lua: -------------------------------------------------------------------------------- 1 | -- Music module API used to trigger Music module with headset changing status -- 2 | 3 | -- Module background code 4 | function BackgroundMusic() 5 | Sound.updateStream() 6 | 7 | -- Cycle mode 8 | if cycle_index > 1 then 9 | if Sound.getTime(current_song) >= Sound.getTotalTime(current_song) then 10 | Sound.pause(current_song) 11 | Sound.close(current_song) 12 | if cycle_index == 2 then 13 | song_idx = song_idx + 1 14 | if song_idx > #my_songs then 15 | song_idx = 1 16 | end 17 | elseif cycle_index == 3 then 18 | tmp_idx = song_idx + 1 19 | found = false 20 | while tmp_idx < #my_songs do 21 | if my_songs[tmp_idx][3] == current_subfolder then 22 | song_idx = tmp_idx 23 | found = true 24 | break 25 | end 26 | tmp_idx = tmp_idx + 1 27 | end 28 | if not found then 29 | tmp_idx = 1 30 | while tmp_idx < #my_songs do 31 | if my_songs[tmp_idx][3] == current_subfolder then 32 | song_idx = tmp_idx 33 | found = true 34 | break 35 | end 36 | tmp_idx = tmp_idx + 1 37 | end 38 | end 39 | end 40 | if my_songs[song_idx][2] == "WAV" then 41 | current_song = Sound.openWav(my_songs[song_idx][4].."/"..my_songs[song_idx][1],true) 42 | elseif my_songs[song_idx][2] == "AIFF" then 43 | current_song = Sound.openAiff(my_songs[song_idx][4].."/"..my_songs[song_idx][1],true) 44 | end 45 | Sound.play(current_song,NO_LOOP,0x08,0x09) 46 | current_subfolder = my_songs[song_idx][3] 47 | end 48 | end 49 | end 50 | 51 | not_started = false 52 | cycle_index = 2 53 | my_songs = {} 54 | function AddSongsFromDir(dir,album) 55 | tmp = System.listDirectory(dir) 56 | for i,file in pairs(tmp) do 57 | if not file.directory then 58 | tmp_file = io.open(dir.."/"..file.name,FREAD) 59 | magic = io.read(tmp_file,0,4) 60 | if magic == "RIFF" then 61 | table.insert(my_songs,{file.name,"WAV",album,dir}) 62 | elseif magic == "FORM" then 63 | table.insert(my_songs,{file.name,"AIFF",album,dir}) 64 | end 65 | io.close(tmp_file) 66 | else 67 | AddSongsFromDir(dir.."/"..file.name,file.name) 68 | end 69 | end 70 | end 71 | AddSongsFromDir("/MUSIC",nil) 72 | if my_songs[1][2] == "WAV" then 73 | current_song = Sound.openWav(my_songs[1][4].."/"..my_songs[1][1],true) 74 | elseif my_songs[1][2] == "AIFF" then 75 | current_song = Sound.openAiff(my_songs[1][4].."/"..my_songs[1][1],true) 76 | end 77 | Sound.play(current_song,NO_LOOP,0x08,0x09) 78 | current_subfolder = my_songs[1][3] 79 | song_idx = 1 80 | AddIconTopbar(theme_dir.."/images/music_icon.jpg","Music") 81 | table.insert(bg_apps,{BackgroundMusic,MusicGC,"Music"}) -- Starting background Music module -------------------------------------------------------------------------------- /LUA/modules/record.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Record" mode 2 | mode = "Record" 3 | 4 | -- Internal module settings 5 | local does_reg_exist = false 6 | local is_recording = false 7 | local state = "Stopped" 8 | 9 | -- Rendering functions 10 | function AppTopScreenRender() 11 | Graphics.fillRect(5,395,40,220,black) 12 | Graphics.fillRect(6,394,41,219,white) 13 | end 14 | 15 | function AppBottomScreenRender() 16 | end 17 | 18 | -- Module main cycle 19 | function AppMainCycle() 20 | 21 | -- Checking if recording finished 22 | if is_recording then 23 | if state == "Recording" and not Mic.isRecording() then 24 | does_reg_exist = true 25 | state = "Stopped" 26 | cur_sound = Mic.stop() 27 | is_recording = false 28 | end 29 | end 30 | 31 | -- Showing Controls and Info 32 | if not is_recording then 33 | TopCropPrint(9,45,"Press A to start recording",black,TOP_SCREEN) 34 | else 35 | TopCropPrint(9,45,"Press A to stop recording",black,TOP_SCREEN) 36 | TopCropPrint(9,60,"Press R to pause/resume recording",black,TOP_SCREEN) 37 | end 38 | if does_reg_exist then 39 | TopCropPrint(9,75,"Press X to listen recorded sound",black,TOP_SCREEN) 40 | TopCropPrint(9,90,"Press Y to save recorded sound",black,TOP_SCREEN) 41 | end 42 | TopCropPrint(9,200,"State: "..state,black,TOP_SCREEN) 43 | 44 | -- Sets controls triggering 45 | if Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 46 | if is_recording then 47 | tmp = Mic.stop() 48 | Sound.close(tmp) 49 | elseif does_reg_exist then 50 | if Sound.isPlaying(cur_sound) then 51 | Sound.pause(cur_sound) 52 | end 53 | Sound.close(cur_sound) 54 | end 55 | CallMainMenu() 56 | elseif Controls.check(pad,KEY_R) or Controls.check(pad,KEY_R) then 57 | if is_recording then 58 | if Mic.isRecording() then 59 | Mic.pause() 60 | state = "Paused" 61 | else 62 | Mic.resume() 63 | state = "Recording" 64 | end 65 | end 66 | elseif Controls.check(pad,KEY_X) or Controls.check(pad,KEY_X) then 67 | if does_reg_exist then 68 | Sound.play(cur_sound, NO_LOOP) 69 | end 70 | elseif Controls.check(pad,KEY_Y) or Controls.check(pad,KEY_Y) then 71 | if does_reg_exist then 72 | h,m,s = System.getTime() 73 | Sound.saveWav(cur_sound,"/MUSIC/"..h.."-"..m.."-"..s..".wav") 74 | ShowWarning("Sound saved successfully!") 75 | end 76 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 77 | if is_recording then 78 | cur_sound = Mic.stop() 79 | is_recording = false 80 | does_reg_exist = true 81 | state = "Stopped" 82 | else 83 | if does_reg_exist then 84 | Sound.close(cur_sound) 85 | does_reg_exist = false 86 | end 87 | is_recording = true 88 | Mic.start(10, 32730) 89 | state = "Recording" 90 | end 91 | end 92 | end -------------------------------------------------------------------------------- /LUA/modules/info.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Info" mode 2 | mode = "Info" 3 | 4 | -- Internal module settings 5 | mac_addr = Network.getMacAddress() 6 | if Network.isWifiEnabled() then 7 | oldn = true 8 | ip_addr = Network.getIPAddress() 9 | else 10 | oldn = false 11 | ip_addr = "0.0.0.0" 12 | end 13 | usr = System.getUsername() 14 | day, mnth = System.getBirthday() 15 | model = System.getModel() 16 | region = System.getRegion() 17 | fw1,fw2,fw3 = System.getFirmware() 18 | fw = fw1 .. "." .. fw2 .. "-" .. fw3 19 | k1,k2,k3 = System.getKernel() 20 | kernel = k1 .. "." .. k2 .. "-" .. k3 21 | free_space = System.getFreeSpace() 22 | sorting = "Bytes" 23 | if free_space > 1024 then 24 | sorting = "KBs" 25 | free_space = free_space / 1024 26 | if free_space > 1024 then 27 | sorting = "MBs" 28 | free_space = free_space / 1024 29 | end 30 | end 31 | free_space = string.format("%8.2f", free_space) 32 | 33 | -- Rendering functions 34 | function AppTopScreenRender() 35 | Graphics.fillRect(5,395,40,220,black) 36 | Graphics.fillRect(6,394,41,219,white) 37 | end 38 | 39 | function AppBottomScreenRender() 40 | end 41 | 42 | -- Module main cycle 43 | function AppMainCycle() 44 | 45 | -- Draw console info 46 | if model == 1 then 47 | Font.print(ttf,9,45,"Model: 3DS XL",black,TOP_SCREEN) 48 | elseif model == 2 then 49 | Font.print(ttf,9,45,"Model: New 3DS",black,TOP_SCREEN) 50 | elseif model == 3 then 51 | Font.print(ttf,9,45,"Model: 2DS",black,TOP_SCREEN) 52 | elseif model == 4 then 53 | Font.print(ttf,9,45,"Model: New 3DS XL",black,TOP_SCREEN) 54 | else 55 | Font.print(ttf,9,45,"Model: 3DS",black,TOP_SCREEN) 56 | end 57 | if region == 1 then 58 | Font.print(ttf,9,60,"Region: USA",black,TOP_SCREEN) 59 | elseif region == 2 then 60 | Font.print(ttf,9,60,"Region: EUR",black,TOP_SCREEN) 61 | else 62 | Font.print(ttf,9,60,"Region: JPN",black,TOP_SCREEN) 63 | end 64 | 65 | -- Update IP Address 66 | if oldn and not Network.isWifiEnabled() then 67 | ip_addr = "0.0.0.0" 68 | oldn = false 69 | elseif Network.isWifiEnabled() and not oldn then 70 | oldn = true 71 | if build ~= "Ninjhax 2" then 72 | Socket.init() 73 | ip_addr = Network.getIPAddress() 74 | Socket.term() 75 | else 76 | ip_addr = "0.0.0.0" 77 | end 78 | end 79 | 80 | -- Draw info 81 | Font.print(ttf,9,75,"Username: " .. usr,black,TOP_SCREEN) 82 | Font.print(ttf,9,90,"Birthday: " .. day .. " " .. months[mnth],black,TOP_SCREEN) 83 | Font.print(ttf,9,105,"Firmware Build: " .. fw,black,TOP_SCREEN) 84 | Font.print(ttf,9,120,"Kernel Build: " .. kernel,black,TOP_SCREEN) 85 | Font.print(ttf,9,135,"Free Space: "..free_space.." "..sorting,black,TOP_SCREEN) 86 | Font.print(ttf,9,150,"MAC Address: "..mac_addr,black,TOP_SCREEN) 87 | Font.print(ttf,9,165,"IP Address: "..ip_addr,black,TOP_SCREEN) 88 | Font.print(ttf,9,180,"Build: "..build,black,TOP_SCREEN) 89 | 90 | -- Sets controls triggering 91 | if Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 92 | CallMainMenu() 93 | end 94 | 95 | end -------------------------------------------------------------------------------- /LUA/modules/mail.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Mail" mode 2 | mode = "Mail" 3 | 4 | -- Internal module settings 5 | if Network.isWifiEnabled() then 6 | object = "Mail Object" 7 | to = "sample@gmail.com" 8 | body = "This is my e-mail body." 9 | else 10 | ShowError("You need to be connected on Internet to send mails.") 11 | CallMainMenu() 12 | end 13 | CallKeyboard(70,20) 14 | state = "Main" 15 | screenshots = false 16 | 17 | -- Rendering functions 18 | function AppTopScreenRender() 19 | Graphics.fillRect(5,395,40,220,black) 20 | Graphics.fillRect(6,394,41,219,white) 21 | end 22 | 23 | function AppBottomScreenRender() 24 | ShowKeyboard() 25 | Graphics.fillRect(5,315,190,230,black) 26 | Graphics.fillRect(6,314,191,229,white) 27 | end 28 | 29 | -- Module main cycle 30 | function AppMainCycle() 31 | 32 | -- Draw mail elements 33 | TopCropPrint(9,45,"Obj: "..object,black,TOP_SCREEN) 34 | TopCropPrint(9,60,"To: "..to,black,TOP_SCREEN) 35 | Font.print(ttf,9,75,"Body:",black,TOP_SCREEN) 36 | CropPrint(9,195,"State: "..state,black,BOTTOM_SCREEN) 37 | text = LinesGenerator(body,90) 38 | for i,line in pairs(text) do 39 | Font.print(ttf,9,line[2],line[1],black,TOP_SCREEN) 40 | end 41 | 42 | -- Keyboard input 43 | if state ~= "Main" then 44 | in_game = true -- Disable START button function 45 | CropPrint(9,213,"Press L to confirm",black,BOTTOM_SCREEN) 46 | if Controls.check(pad,KEY_L) and not Controls.check(oldpad,KEY_L) then 47 | state = "Main" 48 | end 49 | input = KeyboardInput() 50 | if input > 0 then 51 | if state == "Body" then 52 | tmp = body 53 | elseif state == "To" then 54 | tmp = to 55 | else 56 | tmp = object 57 | end 58 | if input == 0x09 then 59 | if string.len(tmp) > 1 then 60 | tmp = string.sub(tmp,1,string.len(tmp)-1) 61 | else 62 | tmp = "" 63 | end 64 | else 65 | tmp = tmp .. string.char(input) 66 | end 67 | if state == "Body" then 68 | body = tmp 69 | elseif state == "To" then 70 | to = tmp 71 | else 72 | object = tmp 73 | end 74 | end 75 | end 76 | 77 | -- Sets controls triggering 78 | if state == "Main" then 79 | if Controls.check(pad,KEY_A) and not Controls.check(oldpad,KEY_A) then 80 | state = "Body" 81 | elseif Controls.check(pad,KEY_X) then 82 | state = "To" 83 | elseif Controls.check(pad,KEY_Y) then 84 | state = "Obj" 85 | elseif Controls.check(pad,KEY_R) and not Controls.check(oldpad,KEY_R) then 86 | if Network.isWifiEnabled() then 87 | if Network.sendMail(to,object,body) then 88 | ShowWarning("Mail sent successfully!") 89 | else 90 | ShowError("An error has occurred while sending mail.") 91 | end 92 | else 93 | ShowError("You need to be connected on Internet to send mails.") 94 | end 95 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 96 | CloseKeyboard() 97 | CallMainMenu() 98 | end 99 | end 100 | end -------------------------------------------------------------------------------- /LUA/modules/camera.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Camera" mode 2 | mode = "Camera" 3 | 4 | -- Internal module settings 5 | update_bottom_screen = true 6 | ui_enabled = false 7 | screenshots = false 8 | SetBottomRefresh(false) 9 | SetTopRefresh(false) 10 | Camera.init(TOP_SCREEN, OUTER_CAM, PHOTO_MODE_NORMAL, false) 11 | local scene = OUTER_CAM 12 | local photo_mode = PHOTO_MODE_NORMAL 13 | local resolution = VGA_RES 14 | local function GetPhotoMode(pm) 15 | if pm == PHOTO_MODE_NORMAL then 16 | return "Normal" 17 | elseif pm == PHOTO_MODE_PORTRAIT then 18 | return "Portrait" 19 | elseif pm == PHOTO_MODE_LANDSCAPE then 20 | return "Landscape" 21 | elseif pm == PHOTO_MODE_NIGHTVIEW then 22 | return "Night Mode" 23 | elseif pm == PHOTO_MODE_LETTER then 24 | return "Letter" 25 | end 26 | end 27 | local function GetResolution(pm) 28 | if pm == VGA_RES then 29 | return "VGA (640x480)" 30 | elseif pm == QVGA_RES then 31 | return "QVGA (320x240)" 32 | elseif pm == QQVGA_RES then 33 | return "QQVGA (160x120)" 34 | elseif pm == CIF_RES then 35 | return "CIF (352x288)" 36 | elseif pm == QCIF_RES then 37 | return "QCIF (176x144)" 38 | elseif pm == DS_RES then 39 | return "NDS (256x192)" 40 | elseif pm == HDS_RES then 41 | return "HDS (512x384)" 42 | elseif pm == CTR_RES then 43 | return "3DS (400x240)" 44 | end 45 | end 46 | 47 | function UpdateBottomScreen() 48 | 49 | -- Clear bottom screen 50 | Screen.clear(BOTTOM_SCREEN) 51 | 52 | -- Show controls info 53 | Screen.debugPrint(0,0, "Controls:", selected, BOTTOM_SCREEN) 54 | Screen.debugPrint(0,25, "L = Take Photo", white, BOTTOM_SCREEN) 55 | Screen.debugPrint(0,40, "X = Swap camera scene", white, BOTTOM_SCREEN) 56 | Screen.debugPrint(0,55, "Y = Change Photo Mode", white, BOTTOM_SCREEN) 57 | Screen.debugPrint(0,70, "A = Change Photo Resolution", white, BOTTOM_SCREEN) 58 | 59 | -- Show Photo settings 60 | Screen.debugPrint(0,100, "Settings: ", selected, BOTTOM_SCREEN) 61 | Screen.debugPrint(0,125, "Photo Mode: " .. GetPhotoMode(photo_mode), white, BOTTOM_SCREEN) 62 | Screen.debugPrint(0,140, "Resolution: " .. GetResolution(resolution), white, BOTTOM_SCREEN) 63 | 64 | end 65 | 66 | -- Rendering functions 67 | function AppTopScreenRender() 68 | end 69 | 70 | function AppBottomScreenRender() 71 | end 72 | 73 | -- Module main cycle 74 | function AppMainCycle() 75 | 76 | if update_bottom_screen then 77 | OneshotPrint(UpdateBottomScreen) 78 | update_bottom_screen = false 79 | end 80 | 81 | -- Show camera scene 82 | Camera.getOutput() 83 | 84 | -- Sets controls triggering 85 | if Controls.check(pad, KEY_L) then 86 | h,m,s = System.getTime() 87 | Camera.takePhoto("/DCIM/"..h.."-"..m.."-"..s..".jpg", resolution, true) 88 | elseif Controls.check(pad, KEY_Y) and not Controls.check(oldpad, KEY_Y) then 89 | photo_mode = photo_mode + 1 90 | if photo_mode > PHOTO_MODE_LETTER then 91 | photo_mode = PHOTO_MODE_NORMAL 92 | end 93 | Camera.init(TOP_SCREEN, scene, photo_mode, false) 94 | update_bottom_screen = true 95 | elseif Controls.check(pad, KEY_A) and not Controls.check(oldpad, KEY_A) then 96 | resolution = resolution + 1 97 | if resolution > CTR_RES then 98 | resolution = VGA_RES 99 | end 100 | update_bottom_screen = true 101 | elseif Controls.check(pad, KEY_X) and not Controls.check(oldpad, KEY_X) then 102 | Camera.term() 103 | if scene == OUTER_CAM then 104 | scene = INNER_CAM 105 | else 106 | scene = OUTER_CAM 107 | end 108 | Camera.init(TOP_SCREEN, scene, photo_mode, false) 109 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 110 | Camera.term() 111 | CallMainMenu() 112 | end 113 | 114 | end -------------------------------------------------------------------------------- /LUA/modules/themes.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Themes" mode 2 | mode = "Themes" 3 | 4 | -- Internal module settings 5 | master_index_t = 0 6 | p_t = 1 7 | my_themes = {} 8 | tmp = System.listDirectory(main_dir.."/themes") 9 | for i,file in pairs(tmp) do 10 | if file.directory then 11 | table.insert(my_themes,file.name) 12 | end 13 | end 14 | 15 | function UpdateBottomScreen() 16 | 17 | -- Showing files list 18 | base_y = 0 19 | for l, file in pairs(my_themes) do 20 | if (base_y > 226) then 21 | break 22 | end 23 | if (l >= master_index_t) then 24 | if (l==p_t) then 25 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 26 | color = selected 27 | else 28 | color = black 29 | end 30 | CropPrint(0,base_y,file,color,BOTTOM_SCREEN) 31 | base_y = base_y + 15 32 | end 33 | end 34 | 35 | end 36 | 37 | -- Rendering functions 38 | function AppTopScreenRender() 39 | end 40 | 41 | function AppBottomScreenRender() 42 | end 43 | 44 | -- Module main cycle 45 | function AppMainCycle() 46 | 47 | UpdateBottomScreen() 48 | 49 | -- Sets controls triggering 50 | if (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 51 | GarbageCollection() 52 | Font.unload(ttf) 53 | System.deleteFile(start_dir.."/config.sun") 54 | config_file = io.open(start_dir.."/config.sun",FCREATE) 55 | io.write(config_file,0,"main_dir = \""..main_dir.."\"\ntheme = \""..my_themes[p_t].."\"",string.len(main_dir)+string.len(my_themes[p_t])+24) 56 | io.close(config_file) 57 | theme_dir = main_dir.."/themes/"..my_themes[p_t] 58 | bg = Screen.loadImage(theme_dir.."/images/bg.jpg") 59 | if System.doesFileExist(theme_dir.."/images/music.jpg") then 60 | ext = ".jpg" 61 | else 62 | ext = ".png" 63 | end 64 | music = Screen.loadImage(theme_dir.."/images/music"..ext) 65 | video = Screen.loadImage(theme_dir.."/images/video"..ext) 66 | info = Screen.loadImage(theme_dir.."/images/info"..ext) 67 | fb = Screen.loadImage(theme_dir.."/images/fb"..ext) 68 | game = Screen.loadImage(theme_dir.."/images/game"..ext) 69 | photo = Screen.loadImage(theme_dir.."/images/photo"..ext) 70 | cia = Screen.loadImage(theme_dir.."/images/cia"..ext) 71 | extdata = Screen.loadImage(theme_dir.."/images/extdata"..ext) 72 | calc = Screen.loadImage(theme_dir.."/images/calc"..ext) 73 | mail = Screen.loadImage(theme_dir.."/images/mail"..ext) 74 | themes = Screen.loadImage(theme_dir.."/images/themes"..ext) 75 | clock = Screen.loadImage(theme_dir.."/images/clock"..ext) 76 | ftp = Screen.loadImage(theme_dir.."/images/ftp"..ext) 77 | charge = Screen.loadImage(theme_dir.."/images/charge"..ext) 78 | b0 = Screen.loadImage(theme_dir.."/images/0"..ext) 79 | b1 = Screen.loadImage(theme_dir.."/images/1"..ext) 80 | b2 = Screen.loadImage(theme_dir.."/images/2"..ext) 81 | b3 = Screen.loadImage(theme_dir.."/images/3"..ext) 82 | b4 = Screen.loadImage(theme_dir.."/images/4"..ext) 83 | b5 = Screen.loadImage(theme_dir.."/images/5"..ext) 84 | tools = {} 85 | table.insert(tools,{game,"/modules/game.lua","Applications"}) 86 | table.insert(tools,{info,"/modules/info.lua","Console Info"}) 87 | table.insert(tools,{photo,"/modules/photo.lua","Photos"}) 88 | table.insert(tools,{music,"/modules/music.lua","Musics"}) 89 | table.insert(tools,{video,"/modules/video.lua","Videos"}) 90 | table.insert(tools,{fb,"/modules/fb.lua","Filebrowser"}) 91 | table.insert(tools,{cia,"/modules/cia.lua","CIA Manager"}) 92 | table.insert(tools,{extdata,"/modules/extdata.lua","Extdata Manager"}) 93 | table.insert(tools,{calc,"/modules/calc.lua","Calc"}) 94 | table.insert(tools,{clock,"/modules/clock.lua","Clock"}) 95 | table.insert(tools,{ftp,"/modules/ftp.lua","FTP Server"}) 96 | table.insert(tools,{mail,"/modules/mail.lua","Mail"}) 97 | table.insert(tools,{themes,"/modules/themes.lua","Theme Manager"}) 98 | ttf = Font.load(theme_dir.."/fonts/main.ttf") 99 | dofile(theme_dir.."/colors.lua") 100 | Font.setPixelSizes(ttf,18) 101 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 102 | CallMainMenu() 103 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 104 | p_t = p_t - 1 105 | if (p_t >= 16) then 106 | master_index_t = p_t - 15 107 | end 108 | update_bottom_screen = true 109 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 110 | p_t = p_t + 1 111 | if (p_t >= 17) then 112 | master_index_t = p_t - 15 113 | end 114 | update_bottom_screen = true 115 | end 116 | if (p_t < 1) then 117 | p_t = #my_themes 118 | if (p_t >= 17) then 119 | master_index_t = p_t - 15 120 | end 121 | elseif (p_t > #my_themes) then 122 | master_index_t = 0 123 | p_t = 1 124 | end 125 | end -------------------------------------------------------------------------------- /LUA/modules/gallery.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Gallery" mode 2 | mode = "Gallery" 3 | 4 | -- Internal module settings 5 | master_index_p = 0 6 | p_p = 1 7 | update_frame = false 8 | update_bottom_screen = true 9 | ui_enabled = false 10 | not_started_p = true 11 | SetBottomRefresh(false) 12 | my_photos = {} 13 | function AddDirPhoto(dir) 14 | tmp = System.listDirectory(dir) 15 | for i,file in pairs(tmp) do 16 | if not file.directory then 17 | if string.upper(string.sub(file.name,-4)) == ".PNG" or string.upper(string.sub(file.name,-4)) == ".BMP" or string.upper(string.sub(file.name,-4)) == ".JPG" then 18 | table.insert(my_photos,{dir.."/"..file.name,file.name}) 19 | end 20 | else 21 | AddDirPhoto(dir.."/"..file.name) 22 | end 23 | end 24 | end 25 | AddDirPhoto("/DCIM") 26 | if #my_photos > 0 then 27 | x_print = 0 28 | y_print = 0 29 | big_image = false 30 | current_photo = Graphics.loadImage(my_photos[1][1]) 31 | width = Graphics.getImageWidth(current_photo) 32 | height = Graphics.getImageHeight(current_photo) 33 | if width > 400 then 34 | width = 400 35 | big_image = true 36 | end 37 | if height > 240 then 38 | height = 240 39 | big_image = true 40 | end 41 | else 42 | ShowError("DCIM folder is empty.") 43 | CallMainMenu() 44 | end 45 | 46 | function UpdateBottomScreen() 47 | 48 | -- Clear bottom screen 49 | Screen.clear(BOTTOM_SCREEN) 50 | 51 | -- Showing files list 52 | base_y = 0 53 | for l, file in pairs(my_photos) do 54 | if (base_y > 226) then 55 | break 56 | end 57 | if (l >= master_index_p) then 58 | if (l==p_p) then 59 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 60 | color = selected 61 | else 62 | color = white 63 | end 64 | CropPrint(0,base_y,file[2],color,BOTTOM_SCREEN) 65 | base_y = base_y + 15 66 | end 67 | end 68 | 69 | end 70 | 71 | -- Rendering functions 72 | function AppTopScreenRender() 73 | 74 | -- Showing current image 75 | if big_image then 76 | Graphics.drawPartialImage(0,0,x_print,y_print,width,height,current_photo) 77 | x,y = Controls.readCirclePad() 78 | if (x < - 100) and (x_print > 0) then 79 | x_print = x_print - 5 80 | if x_print < 0 then 81 | x_print = 0 82 | end 83 | end 84 | if (y > 100) and (y_print > 0) then 85 | y_print = y_print - 5 86 | if y_print < 0 then 87 | y_print = 0 88 | end 89 | end 90 | if (x > 100) and (x_print + width < Graphics.getImageWidth(current_photo)) then 91 | x_print = x_print + 5 92 | end 93 | if (y < - 100) and (y_print + height < Graphics.getImageHeight(current_photo)) then 94 | y_print = y_print + 5 95 | end 96 | if x_print + width > Graphics.getImageWidth(current_photo) then 97 | x_print = Graphics.getImageWidth(current_photo) - width 98 | end 99 | if y_print + height > Graphics.getImageHeight(current_photo) then 100 | y_print = Graphics.getImageHeight(current_photo) - height 101 | end 102 | else 103 | Graphics.drawImage(0,0,current_photo) 104 | end 105 | 106 | end 107 | 108 | function AppBottomScreenRender() 109 | end 110 | 111 | -- Module main cycle 112 | function AppMainCycle() 113 | 114 | if update_bottom_screen then 115 | OneshotPrint(UpdateBottomScreen) 116 | update_bottom_screen = false 117 | end 118 | 119 | -- Update current image 120 | if update_frame then 121 | update_frame = false 122 | Graphics.freeImage(current_photo) 123 | x_print = 0 124 | y_print = 0 125 | big_image = false 126 | current_photo = Graphics.loadImage(my_photos[p_p][1]) 127 | width = Graphics.getImageWidth(current_photo) 128 | height = Graphics.getImageHeight(current_photo) 129 | if width > 400 then 130 | width = 400 131 | big_image = true 132 | end 133 | if height > 240 then 134 | height = 240 135 | big_image = true 136 | end 137 | end 138 | 139 | -- Sets controls triggering 140 | if Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 141 | Graphics.freeImage(current_photo) 142 | CallMainMenu() 143 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) and not_started_p then 144 | p_p = p_p - 1 145 | if (p_p >= 16) then 146 | master_index_p = p_p - 15 147 | end 148 | update_frame = true 149 | update_bottom_screen = true 150 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) and not_started_p then 151 | p_p = p_p + 1 152 | if (p_p >= 17) then 153 | master_index_p = p_p - 15 154 | end 155 | update_frame = true 156 | update_bottom_screen = true 157 | end 158 | if (p_p < 1) then 159 | p_p = #my_photos 160 | if (p_p >= 17) then 161 | master_index_p = p_p - 15 162 | end 163 | elseif (p_p > #my_photos) then 164 | master_index_p = 0 165 | p_p = 1 166 | end 167 | end -------------------------------------------------------------------------------- /LUA/modules/game.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Game" mode 2 | mode = "Game" 3 | 4 | -- Internal module settings 5 | master_index_g = 0 6 | p_g = 1 7 | function CheckDirectory(src,key) 8 | tmp = System.listDirectory(src) 9 | for i,file in pairs(tmp) do 10 | if file.directory then 11 | if file.name == key then 12 | return true 13 | end 14 | end 15 | end 16 | return false 17 | end 18 | my_apps = {} 19 | function ExtCallMainMenu() 20 | System.exit = my_exit 21 | Sound.init() 22 | CallMainMenu() 23 | end 24 | 25 | -- Adding preinstalled LUA homebrews 26 | for i,app in pairs(System.listDirectory(main_dir.."/apps")) do 27 | if app.directory then 28 | dofile(main_dir.."/apps/"..app.name.."/data.lua") 29 | table.insert(my_apps,{true,app.name,app.name,app_desc,app_author,nil,true}) 30 | end 31 | end 32 | 33 | if build == "Ninjhax 1" then 34 | dir = System.listDirectory("/3ds/") 35 | for i,file in pairs(dir) do 36 | if file.directory then 37 | if System.doesFileExist("/3ds/"..(file.name).."/"..(file.name)..".3dsx") then 38 | if System.doesFileExist("/3ds/"..(file.name).."/"..(file.name)..".smdh") then 39 | app = System.extractSMDH("/3ds/"..(file.name).."/"..(file.name)..".smdh") 40 | table.insert(my_apps,{true,file.name,app.title,app.desc,app.author,app.icon,false}) 41 | else 42 | table.insert(my_apps,{true,file.name,file.name,"","",nil,false}) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | if build ~= "Ninjhax 2" then 49 | table.insert(my_apps,{false,nil,"Game Cartridge","","0x0",nil})-- Insert Gamecard voice 50 | dir = System.listCIA() 51 | for i,file in pairs(dir) do 52 | if file.mediatype == SDMC then 53 | table.insert(my_apps,{false,file.unique_id,file.product_id,"","0x"..string.sub(string.format('%02X',file.unique_id),1,-3),nil}) 54 | end 55 | end 56 | 57 | -- Game titles parsing 58 | dofile(main_dir.."/scripts/title_list.lua") 59 | assigned = 0 60 | for i,title in pairs(title_list) do 61 | if assigned >= (#my_apps - 1) then 62 | break 63 | end 64 | for z,app in pairs(my_apps) do 65 | if app[2] == title[3] then 66 | app[3] = title[1] 67 | assigned = assigned + 1 68 | end 69 | end 70 | end 71 | 72 | end 73 | 74 | if #my_apps <= 0 then 75 | ShowError("Cannot access any launchable application.") 76 | CallMainMenu() 77 | end 78 | 79 | -- Rendering functions 80 | function AppTopScreenRender() 81 | Graphics.fillRect(5,395,40,220,black) 82 | Graphics.fillRect(6,394,41,219,white) 83 | end 84 | 85 | function AppBottomScreenRender() 86 | end 87 | 88 | -- Module main cycle 89 | function AppMainCycle() 90 | 91 | -- Draw bottom screen listmenu and top screen info 92 | base_y = 0 93 | for l, file in pairs(my_apps) do 94 | if (base_y > 226) then 95 | break 96 | end 97 | if (l >= master_index_g) then 98 | if (l==p_g) then 99 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 100 | TopCropPrint(9,45,file[3],selected,TOP_SCREEN) 101 | TopCropPrint(9,60,file[5],black,TOP_SCREEN) 102 | gw_rom = System.getGWRomID() 103 | if file[2] == nil and build == "CIA" and gw_rom ~= "" then -- GW roms support 104 | desc = LinesGenerator("This is a 3DS rom probably loaded with a Gateway card. Product-ID: "..gw_rom,90) 105 | else 106 | desc = LinesGenerator(file[4],90) 107 | end 108 | for i,line in pairs(desc) do 109 | Font.print(ttf,9,line[2],line[1],black,TOP_SCREEN) 110 | end 111 | if file[6] ~= nil then 112 | Screen.fillEmptyRect(341,390,43,92,black,TOP_SCREEN) 113 | Screen.drawImage(342,44,file[6],TOP_SCREEN) 114 | end 115 | color = selected 116 | else 117 | color = black 118 | end 119 | if file[1] then 120 | CropPrint(0,base_y,file[2],color,BOTTOM_SCREEN) 121 | else 122 | CropPrint(0,base_y,file[3],color,BOTTOM_SCREEN) 123 | end 124 | base_y = base_y + 15 125 | end 126 | end 127 | 128 | -- Sets controls triggering 129 | if Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 130 | CallMainMenu() 131 | for l, file in pairs(my_apps) do 132 | if file[6] ~= nil then 133 | Screen.freeImage(file[6]) 134 | end 135 | end 136 | elseif Controls.check(pad,KEY_A) then 137 | for i,bg_apps_code in pairs(bg_apps) do 138 | bg_apps_code[2]() 139 | end 140 | for l, file in pairs(my_apps) do 141 | if file[6] ~= nil then 142 | Screen.freeImage(file[6]) 143 | end 144 | end 145 | Sound.term() 146 | if my_apps[p_g][1] then 147 | if my_apps[p_g][7] then 148 | ui_enabled = false 149 | screenshots = false 150 | my_exit = System.exit 151 | in_game = true 152 | System.exit = ExtCallMainMenu 153 | dofile(main_dir.."/apps/"..my_apps[p_g][2].."/index.lua") 154 | else 155 | Font.unload(ttf) 156 | GarbageCollection() 157 | System.launch3DSX("/3ds/"..my_apps[p_g][2].."/"..my_apps[p_g][2]..".3dsx") 158 | end 159 | else 160 | Font.unload(ttf) 161 | GarbageCollection() 162 | if my_apps[p_g][2] == nil then 163 | ShowWarning("You will be redirected to sysNand and your gamecard will be launched.") 164 | System.launchGamecard() 165 | else 166 | System.launchCIA(my_apps[p_g][2],SDMC) 167 | end 168 | end 169 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 170 | p_g = p_g - 1 171 | if (p_g >= 16) then 172 | master_index_g = p_g - 15 173 | end 174 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 175 | p_g = p_g + 1 176 | if (p_g >= 17) then 177 | master_index_g = p_g - 15 178 | end 179 | end 180 | if (p_g < 1) then 181 | p_g = #my_apps 182 | if (p_g >= 17) then 183 | master_index_g = p_g - 15 184 | end 185 | elseif (p_g > #my_apps) then 186 | master_index_g = 0 187 | p_g = 1 188 | end 189 | end -------------------------------------------------------------------------------- /LUA/modules/music.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Music" mode 2 | mode = "Music" 3 | 4 | -- Internal module settings 5 | FreeIconTopbar("Music") 6 | hide = false 7 | master_index_m = 0 8 | text_for_top_screen = "" 9 | p_m = 1 10 | if not_started == nil then 11 | not_started = true 12 | end 13 | my_songs = {} 14 | cycle_mode = {"No Cycle","All","Subfolder","Song"} 15 | if cycle_index == nil then 16 | cycle_index = 1 17 | end 18 | function AddSongsFromDir(dir,album) 19 | tmp = System.listDirectory(dir) 20 | for i,file in pairs(tmp) do 21 | if not file.directory then 22 | tmp_file = io.open(dir.."/"..file.name,FREAD) 23 | magic = io.read(tmp_file,0,4) 24 | if magic == "RIFF" then 25 | table.insert(my_songs,{file.name,"WAV",album,dir}) 26 | elseif magic == "FORM" then 27 | table.insert(my_songs,{file.name,"AIFF",album,dir}) 28 | elseif magic == "OggS" then 29 | table.insert(my_songs,{file.name,"OGG",album,dir}) 30 | end 31 | io.close(tmp_file) 32 | else 33 | AddSongsFromDir(dir.."/"..file.name,file.name) 34 | end 35 | end 36 | end 37 | AddSongsFromDir("/MUSIC",nil) 38 | blue = Color.new(0,0,255) 39 | 40 | if #my_songs <= 0 then 41 | ShowError("MUSIC folder is empty.") 42 | CallMainMenu() 43 | end 44 | 45 | -- Module background code 46 | function BackgroundMusic() 47 | Sound.updateStream() 48 | 49 | -- Cycle mode 50 | if cycle_index > 1 then 51 | if Sound.getTime(current_song) >= Sound.getTotalTime(current_song) then 52 | update_list = true 53 | Sound.pause(current_song) 54 | Sound.close(current_song) 55 | if cycle_index == 2 then 56 | song_idx = song_idx + 1 57 | if song_idx > #my_songs then 58 | song_idx = 1 59 | end 60 | elseif cycle_index == 3 then 61 | tmp_idx = song_idx + 1 62 | found = false 63 | while tmp_idx < #my_songs do 64 | if my_songs[tmp_idx][3] == current_subfolder then 65 | song_idx = tmp_idx 66 | found = true 67 | break 68 | end 69 | tmp_idx = tmp_idx + 1 70 | end 71 | if not found then 72 | tmp_idx = 1 73 | while tmp_idx < #my_songs do 74 | if my_songs[tmp_idx][3] == current_subfolder then 75 | song_idx = tmp_idx 76 | found = true 77 | break 78 | end 79 | tmp_idx = tmp_idx + 1 80 | end 81 | end 82 | end 83 | if my_songs[song_idx][2] == "WAV" then 84 | current_song = Sound.openWav(my_songs[song_idx][4].."/"..my_songs[song_idx][1],true) 85 | elseif my_songs[song_idx][2] == "AIFF" then 86 | current_song = Sound.openAiff(my_songs[song_idx][4].."/"..my_songs[song_idx][1],true) 87 | elseif my_songs[song_idx][2] == "OGG" then 88 | current_song = Sound.openOgg(my_songs[song_idx][4].."/"..my_songs[song_idx][1],true) 89 | end 90 | Sound.play(current_song,NO_LOOP) 91 | current_subfolder = my_songs[song_idx][3] 92 | end 93 | end 94 | end 95 | 96 | function ShowFileList() 97 | base_y = 0 98 | for l, file in pairs(my_songs) do 99 | if (base_y > 226) then 100 | break 101 | end 102 | if (l >= master_index_m) then 103 | if (l==p_m) then 104 | if file[3] ~= nil then 105 | text_for_top_screen = "Subfolder: "..file[3] 106 | else 107 | text_for_top_screen = "Subfolder: None" 108 | end 109 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 110 | color = selected 111 | else 112 | if (l==song_idx) then 113 | color = blue 114 | else 115 | color = black 116 | end 117 | end 118 | CropPrint(0,base_y,file[1],color,BOTTOM_SCREEN) 119 | base_y = base_y + 15 120 | end 121 | end 122 | end 123 | 124 | -- Internal Module GarbageCollection 125 | function MusicGC() 126 | if current_song ~= nil then 127 | Sound.pause(current_song) 128 | Sound.close(current_song) 129 | current_song = nil 130 | end 131 | end 132 | 133 | -- Rendering functions 134 | function AppTopScreenRender() 135 | Graphics.fillRect(5,395,40,220,black) 136 | Graphics.fillRect(6,394,41,219,white) 137 | end 138 | 139 | function AppBottomScreenRender() 140 | end 141 | 142 | -- Module main cycle 143 | function AppMainCycle() 144 | 145 | -- Draw Cycle Mode info 146 | Font.print(ttf,9,200,"Cycle mode: "..cycle_mode[cycle_index],black,TOP_SCREEN) 147 | 148 | -- Showing files list 149 | ShowFileList() 150 | 151 | -- Showing Song info 152 | if not not_started then 153 | TopCropPrint(9,45,"Title: "..Sound.getTitle(current_song),black,TOP_SCREEN) 154 | TopCropPrint(9,60,"Author: "..Sound.getAuthor(current_song),black,TOP_SCREEN) 155 | if my_songs[song_idx][3] ~= nil then 156 | Font.print(ttf,9,75,"Subfolder: "..my_songs[song_idx][3],black,TOP_SCREEN) 157 | else 158 | Font.print(ttf,9,75,"Subfolder: None",black,TOP_SCREEN) 159 | end 160 | if Sound.getType(current_song) == 1 then 161 | TopCropPrint(9,90,"Audiotype: Mono",black,TOP_SCREEN) 162 | else 163 | TopCropPrint(9,90,"Audiotype: Stereo",black,TOP_SCREEN) 164 | end 165 | TopCropPrint(9,105,"Time: "..FormatTime(Sound.getTime(current_song)).." / "..FormatTime(Sound.getTotalTime(current_song)),black,TOP_SCREEN) 166 | TopCropPrint(9,120,"Samplerate: "..Sound.getSrate(current_song),black,TOP_SCREEN) 167 | else 168 | Font.print(ttf,9,45,text_for_top_screen,black,TOP_SCREEN) 169 | end 170 | 171 | -- Power-on screens triggering 172 | if hide then 173 | if pad ~= 0x00 then 174 | hide = false 175 | Controls.enableScreen(TOP_SCREEN) 176 | Controls.enableScreen(BOTTOM_SCREEN) 177 | end 178 | end 179 | 180 | -- Sets controls triggering 181 | if (Controls.check(pad,KEY_SELECT)) and not (Controls.check(oldpad,KEY_SELECT)) and not (not_started) then 182 | CallMainMenu() 183 | AddIconTopbar(theme_dir.."/images/music_icon.jpg","Music") 184 | elseif (Controls.check(pad,KEY_R)) and not (Controls.check(oldpad,KEY_R)) and not (not_started) then 185 | if not hide then 186 | Controls.disableScreen(TOP_SCREEN) 187 | Controls.disableScreen(BOTTOM_SCREEN) 188 | hide = true 189 | end 190 | elseif (Controls.check(pad,KEY_X)) and not (Controls.check(oldpad,KEY_X)) and not (not_started) then 191 | if Sound.isPlaying(current_song) then 192 | Sound.pause(current_song) 193 | else 194 | Sound.resume(current_song) 195 | end 196 | elseif (Controls.check(pad,KEY_Y)) and not (Controls.check(oldpad,KEY_Y)) and not (not_started) then 197 | not_started = true 198 | CloseBGApp("Music") 199 | current_song = nil 200 | elseif (Controls.check(pad,KEY_DLEFT)) and not (Controls.check(oldpad,KEY_DLEFT)) then 201 | cycle_index = cycle_index - 1 202 | if cycle_index < 1 then 203 | cycle_index = 4 204 | end 205 | elseif (Controls.check(pad,KEY_DRIGHT)) and not (Controls.check(oldpad,KEY_DRIGHT)) then 206 | cycle_index = cycle_index + 1 207 | if cycle_index > 4 then 208 | cycle_index = 1 209 | end 210 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 211 | if current_song ~= nil then 212 | MusicGC() 213 | end 214 | not_started = false 215 | if my_songs[p_m][2] == "WAV" then 216 | current_song = Sound.openWav(my_songs[p_m][4].."/"..my_songs[p_m][1],true) 217 | elseif my_songs[p_m][2] == "AIFF" then 218 | current_song = Sound.openAiff(my_songs[p_m][4].."/"..my_songs[p_m][1],true) 219 | elseif my_songs[p_m][2] == "OGG" then 220 | current_song = Sound.openOgg(my_songs[p_m][4].."/"..my_songs[p_m][1],true) 221 | end 222 | Sound.play(current_song,NO_LOOP) 223 | current_subfolder = my_songs[p_m][3] 224 | song_idx = p_m 225 | table.insert(bg_apps,{BackgroundMusic,MusicGC,"Music"}) -- Starting background Music module 226 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 227 | CallMainMenu() 228 | not_started = true 229 | if current_song ~= nil then 230 | CloseBGApp("Music") 231 | end 232 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 233 | p_m = p_m - 1 234 | if (p_m >= 16) then 235 | master_index_m = p_m - 15 236 | end 237 | update_list = true 238 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 239 | p_m = p_m + 1 240 | if (p_m >= 17) then 241 | master_index_m = p_m - 15 242 | end 243 | update_list = true 244 | end 245 | if (p_m < 1) then 246 | p_m = #my_songs 247 | if (p_m >= 17) then 248 | master_index_m = p_m - 15 249 | end 250 | elseif (p_m > #my_songs) then 251 | master_index_m = 0 252 | p_m = 1 253 | end 254 | end -------------------------------------------------------------------------------- /LUA/modules/clock.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Clock" mode 2 | mode = "Clock" 3 | 4 | -- Internal module settings 5 | FreeIconTopbar("Clock") 6 | screenshots = false 7 | function Cronometer(millisecs) 8 | secs = millisecs / 1000 9 | mins = secs / 60 10 | hours = math.floor(mins / 60) 11 | mins = math.floor(mins % 60) 12 | secs = math.floor(secs % 60) 13 | millisecs = math.floor(millisecs % 1000) 14 | if mins < 10 then 15 | mins = "0"..mins 16 | end 17 | if secs < 10 then 18 | secs = "0"..secs 19 | end 20 | while string.len(tostring(millisecs)) < 3 do 21 | millisecs = "0"..millisecs 22 | end 23 | return hours..":"..mins..":"..secs..":"..millisecs 24 | end 25 | cronometer = false 26 | crono_time = math.tointeger(0) 27 | 28 | -- Module background code 29 | function BackgroundClock() 30 | if my_alarm_time[1] == nil then -- Countdown 31 | if Timer.getTime(count_crono) / 1000 > my_alarm_time[3] then 32 | Sound.play(alarm_sound,LOOP,0x08,0x09) 33 | ShowWarning("Countdown ended.") 34 | CloseBGApp("Clock") 35 | end 36 | else -- Alarm 37 | 38 | -- Blit Alarm alert on Main Menu 39 | if module == "Main Menu" then 40 | Screen.fillEmptyRect(5,200,210,230,black,TOP_SCREEN) 41 | Screen.fillRect(6,199,211,229,white,TOP_SCREEN) 42 | Font.print(ttf,8,214,"Alarm: "..my_alarm_time[1]..":"..my_alarm_time[2]..":"..my_alarm_time[3],black,TOP_SCREEN) 43 | end 44 | 45 | h,m,s = System.getTime() 46 | if h == tonumber(my_alarm_time[1]) and m == tonumber(my_alarm_time[2]) and s == tonumber(my_alarm_time[3]) then 47 | Sound.play(alarm_sound,LOOP,0x08,0x09) 48 | ShowWarning("Alarm clock!") 49 | CloseBGApp("Clock") 50 | end 51 | end 52 | end 53 | 54 | function ClockGC() 55 | FreeIconTopbar("Clock") 56 | if Sound.isPlaying(alarm_sound) then 57 | Sound.pause(alarm_sound) 58 | end 59 | Sound.close(alarm_sound) 60 | if my_alarm_time[1] == nil then 61 | Timer.destroy(count_crono) 62 | end 63 | set_alarm = nil 64 | alarm_start = nil 65 | alarm_sound = nil 66 | end 67 | 68 | -- Rendering functions 69 | function AppTopScreenRender() 70 | Graphics.fillRect(5,395,40,220,black) 71 | Graphics.fillRect(6,394,41,219,white) 72 | end 73 | 74 | function AppBottomScreenRender() 75 | 76 | if set_alarm ~= nil then 77 | Graphics.fillRect(5,100,75,95,black) 78 | Graphics.fillRect(6,99,76,94,white) 79 | end 80 | 81 | end 82 | 83 | -- Module main cycle 84 | function AppMainCycle() 85 | 86 | -- Draw controls info 87 | h,m,s = System.getTime() 88 | if m < 10 then 89 | m = "0"..m 90 | end 91 | if s < 10 then 92 | s = "0"..s 93 | end 94 | Font.print(ttf,9,45,"Current time: "..h..":"..m..":"..s,black,TOP_SCREEN) 95 | if cronometer then 96 | crono_time = Timer.getTime(crono) 97 | Font.print(ttf,9,70,"A = Resume/Pause Chronometer",black,TOP_SCREEN) 98 | else 99 | Font.print(ttf,9,70,"A = Start Chronometer",black,TOP_SCREEN) 100 | end 101 | Font.print(ttf,9,85,"X = Set Countdown",black,TOP_SCREEN) 102 | Font.print(ttf,9,100,"Y = Set Alarm",black,TOP_SCREEN) 103 | Font.print(ttf,9,115,"L = Start Countdown/Alarm",black,TOP_SCREEN) 104 | Font.print(ttf,9,130,"R = Stop Countdown/Alarm",black,TOP_SCREEN) 105 | Font.print(ttf,9,145,"SELECT = Reset Chronometer",black,TOP_SCREEN) 106 | Font.print(ttf,9,160,"B = Return Main Menu",black,TOP_SCREEN) 107 | 108 | -- Reset x,y coordinates 109 | y = 50 110 | x = 15 111 | 112 | -- Sets keyboard triggering 113 | i = 1 114 | z = 1 115 | x_c = x+195 116 | y_c = y-5 117 | while (i <= 12) do 118 | exec = false 119 | Screen.fillEmptyRect(x_c,x_c+30,y_c,y_c+25,black,BOTTOM_SCREEN) 120 | Screen.fillRect(x_c+1,x_c+29,y_c+1,y_c+24,white,BOTTOM_SCREEN) 121 | if Controls.check(pad,KEY_TOUCH) and not Controls.check(oldpad,KEY_TOUCH) then 122 | c1,c2 = Controls.readTouch() 123 | if c1 >= x_c and c2 >= y_c then 124 | if c1 < x_c + 25 and c2 < y_c + 25 then 125 | exec = true 126 | end 127 | end 128 | end 129 | if exec then 130 | if set_alarm ~= nil then 131 | if set_alarm and j < 7 and i < 11 then 132 | if i == 10 then 133 | alarm_table[j] = 0 134 | else 135 | alarm_table[j] = i 136 | end 137 | j=j+1 138 | if alarm_table[1] >= 2 then 139 | alarm_table[1] = 2 140 | if alarm_table[2] > 3 then 141 | alarm_table[2] = 3 142 | end 143 | end 144 | if alarm_table[3] > 5 then 145 | alarm_table[3] = 5 146 | end 147 | if alarm_table[5] > 5 then 148 | alarm_table[5] = 5 149 | end 150 | elseif set_alarm and i > 10 then 151 | if i == 11 then 152 | if j > 1 then 153 | alarm_table[j-1] = 0 154 | j = j - 1 155 | end 156 | else 157 | j = 1 158 | alarm_table = {0,0,0,0,0,0} 159 | end 160 | else 161 | if i < 10 then 162 | if countdown_time == 0 then 163 | countdown_time = i 164 | else 165 | countdown_time = tonumber(tostring(countdown_time)..i) 166 | end 167 | else 168 | if i == 11 then 169 | countdown_time = math.floor(countdown_time / 10) 170 | elseif i == 12 then 171 | countdown_time = 0 172 | else 173 | countdown_time = tonumber(tostring(countdown_time)..0) 174 | end 175 | end 176 | end 177 | end 178 | end 179 | i=i+1 180 | z=z+1 181 | x_c = x_c + 30 182 | if z > 3 then 183 | x_c = x+195 184 | y_c = y_c + 25 185 | z = 1 186 | end 187 | end 188 | 189 | -- Draw alarm config 190 | if set_alarm ~= nil then 191 | if set_alarm then 192 | k = 1 193 | my_alarm = "" 194 | while k < 7 do 195 | my_alarm = my_alarm .. alarm_table[k] 196 | if (k == 2 or k == 4) then 197 | my_alarm = my_alarm .. ":" 198 | end 199 | k = k + 1 200 | end 201 | Font.print(ttf,9,80,my_alarm,black,BOTTOM_SCREEN) 202 | end 203 | end 204 | 205 | -- Draw numeric keyboard 206 | Font.print(ttf,x+200,y,"1",black,BOTTOM_SCREEN) 207 | Font.print(ttf,x+230,y,"2",black,BOTTOM_SCREEN) 208 | Font.print(ttf,x+260,y,"3",black,BOTTOM_SCREEN) 209 | Font.print(ttf,x+200,y+25,"4",black,BOTTOM_SCREEN) 210 | Font.print(ttf,x+230,y+25,"5",black,BOTTOM_SCREEN) 211 | Font.print(ttf,x+260,y+25,"6",black,BOTTOM_SCREEN) 212 | Font.print(ttf,x+200,y+50,"7",black,BOTTOM_SCREEN) 213 | Font.print(ttf,x+230,y+50,"8",black,BOTTOM_SCREEN) 214 | Font.print(ttf,x+260,y+50,"9",black,BOTTOM_SCREEN) 215 | Font.print(ttf,x+200,y+75,"0",black,BOTTOM_SCREEN) 216 | Font.print(ttf,x+230,y+75,"D",black,BOTTOM_SCREEN) 217 | Font.print(ttf,x+260,y+75,"C",black,BOTTOM_SCREEN) 218 | 219 | -- Draw Cronometer/Alarm stats 220 | Font.print(ttf,9,185,"Chronometer: "..Cronometer(crono_time),black,TOP_SCREEN) 221 | if alarm_start ~= nil then 222 | if my_alarm_time[1] == nil then 223 | Font.print(ttf,9,198,"Countdown set for: "..my_alarm_time[3].." seconds.",black,TOP_SCREEN) 224 | else 225 | Font.print(ttf,9,198,"Alarm set for: "..my_alarm_time[1]..":"..my_alarm_time[2]..":"..my_alarm_time[3],black,TOP_SCREEN) 226 | end 227 | end 228 | 229 | -- Sets controls triggering 230 | if Controls.check(pad,KEY_R) and not Controls.check(oldpad,KEY_R) and alarm_start ~= nil then 231 | CloseBGApp("Clock") 232 | elseif Controls.check(pad,KEY_L) and not Controls.check(oldpad,KEY_L) and set_alarm ~= nil then 233 | alarm_sound = Sound.openOgg(theme_dir.."/sounds/alarm.ogg") 234 | if set_alarm then 235 | alarm_start = false 236 | hours = alarm_table[1]..alarm_table[2] 237 | minutes = alarm_table[3]..alarm_table[4] 238 | secs = alarm_table[5]..alarm_table[6] 239 | my_alarm_time = {hours,minutes,secs} 240 | else 241 | my_alarm_time = {nil,nil,countdown_time} 242 | count_crono = Timer.new() 243 | alarm_start = false 244 | end 245 | set_alarm = nil 246 | table.insert(bg_apps,{BackgroundClock,ClockGC,"Clock"}) -- Adding Clock module to background apps 247 | elseif Controls.check(pad,KEY_A) and not Controls.check(oldpad,KEY_A) then 248 | if not cronometer then 249 | crono = Timer.new() 250 | cronometer = true 251 | else 252 | if Timer.isPlaying(crono) then 253 | Timer.pause(crono) 254 | else 255 | Timer.resume(crono) 256 | end 257 | end 258 | elseif Controls.check(pad,KEY_SELECT) and not Controls.check(oldpad,KEY_SELECT) then 259 | if cronometer then 260 | Timer.reset(crono) 261 | end 262 | elseif Controls.check(pad,KEY_Y) and not Controls.check(oldpad,KEY_Y) then 263 | set_alarm = true 264 | alarm_table = {0,0,0,0,0,0} 265 | j=1 266 | elseif Controls.check(pad,KEY_X) and not Controls.check(oldpad,KEY_X) then 267 | set_alarm = false 268 | countdown_time = 0 269 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 270 | if cronometer then 271 | Timer.destroy(crono) 272 | end 273 | if alarm_sound ~= nil then 274 | AddIconTopbar(theme_dir.."/images/clock_icon.jpg","Clock") 275 | end 276 | CallMainMenu() 277 | end 278 | end -------------------------------------------------------------------------------- /LUA/modules/extdata.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Extdata" mode 2 | mode = "Extdata" 3 | 4 | -- Internal module settings 5 | if extdata_backup == nil then 6 | ShowWarning("Extdata listing will take time to be generated. Press OK to start list generation.") 7 | files_table = System.scanExtdata() 8 | extdata_backup = files_table 9 | end 10 | DisableRenderer() 11 | update_bottom_screen = true 12 | update_top_screen = true 13 | ui_enabled = false 14 | extdata_directory = "/" 15 | white = Color.new(255,255,255) 16 | black = Color.new(0,0,0) 17 | red = Color.new(255,0,0) 18 | green = Color.new(0,255,0) 19 | menu_color = Color.new(255,255,255) 20 | selected_color = Color.new(0,255,0) 21 | selected_item = Color.new(0,0,200,50) 22 | hex_values = {} 23 | hex_text = {} 24 | updateTXT = false 25 | select_mode = false 26 | update_main_extdata = true 27 | action_check = false 28 | move_base = nil 29 | copy_base = nil 30 | x_print = 0 31 | y_print = 0 32 | MAX_RAM_ALLOCATION = 10485760 33 | copy_type = 0 34 | move_type = 0 35 | txt_index = 0 36 | txt_words = 0 37 | txt_i = 0 38 | old_indexes = {} 39 | p = 1 40 | master_index = 0 41 | function DumpFile(input,archive) 42 | inp = io.open(extdata_directory..input,FREAD,archive) 43 | if System.doesFileExist("/"..input) then 44 | System.deleteFile("/"..input) 45 | end 46 | out = io.open("/"..string.format('%02X',archive).."_"..input,FCREATE) 47 | size = io.size(inp) 48 | index = 0 49 | while (index+(MAX_RAM_ALLOCATION/2) < size) do 50 | io.write(out,index,io.read(inp,index,MAX_RAM_ALLOCATION/2),(MAX_RAM_ALLOCATION/2)) 51 | index = index + (MAX_RAM_ALLOCATION/2) 52 | end 53 | if index < size then 54 | io.write(out,index,io.read(inp,index,size-index),(size-index)) 55 | end 56 | io.close(inp) 57 | io.close(out) 58 | end 59 | function RestoreFile(input,archive) 60 | inp = io.open("/"..string.format('%02X',archive).."_"..input,FREAD) 61 | out = io.open(extdata_directory..input,FWRITE,archive) 62 | if io.size(inp) <= io.size(out) then 63 | size = io.size(inp) 64 | index = 0 65 | while (index+(MAX_RAM_ALLOCATION/2) < size) do 66 | io.write(out,index,io.read(inp,index,MAX_RAM_ALLOCATION/2),(MAX_RAM_ALLOCATION/2)) 67 | index = index + (MAX_RAM_ALLOCATION/2) 68 | end 69 | if index < size then 70 | io.write(out,index,io.read(inp,index,size-index),(size-index)) 71 | end 72 | end 73 | io.close(inp) 74 | io.close(out) 75 | end 76 | function RestoreFolder(input,archive) 77 | files = System.listDirectory("/"..string.format('%02X',archive).."_"..input) 78 | for z, file in pairs(files) do 79 | if (file.directory) then 80 | RestoreFolder(input.."/"..file.name,archive) 81 | else 82 | RestoreFile(input.."/"..file.name,archive) 83 | end 84 | end 85 | end 86 | function OpenExtdataFile(text, archive) 87 | ExtGC() 88 | current_file = io.open(extdata_directory..text,FREAD,archive) 89 | txt_index = 0 90 | updateTXT = true 91 | end 92 | function OpenDirectory(text,archive_id) 93 | i=0 94 | if text == ".." then 95 | j=-2 96 | while string.sub(extdata_directory,j,j) ~= "/" do 97 | j=j-1 98 | end 99 | extdata_directory = string.sub(extdata_directory,1,j) 100 | else 101 | extdata_directory = extdata_directory..text.."/" 102 | end 103 | if extdata_directory == "/" then 104 | files_table = extdata_backup 105 | else 106 | files_table = System.listExtdataDir(extdata_directory,archive_id) 107 | local extra = {} 108 | extra.name = ".." 109 | extra.size = 0 110 | extra.directory = true 111 | extra.archive = archive_id 112 | table.insert(files_table,extra) 113 | end 114 | end 115 | function ExtGC() 116 | if current_file ~= nil then 117 | update_top_screen = true 118 | io.close(current_file) 119 | old_indexes = {} 120 | txt_i = 0 121 | end 122 | current_file = nil 123 | end 124 | function DumpFolder(input,archive) 125 | files = System.listExtdataDir(extdata_directory..input,archive) 126 | System.createDirectory("/"..string.format('%02X',archive).."_"..input) 127 | for z, file in pairs(files) do 128 | if (file.directory) then 129 | DumpFolder(input.."/"..file.name,archive) 130 | else 131 | DumpFile(input.."/"..file.name,archive) 132 | end 133 | end 134 | end 135 | function BuildHex(file, index) 136 | MAX_LENGTH = 120 137 | SIZE = io.size(file) 138 | if ((index + MAX_LENGTH) < SIZE) then 139 | READ_LENGTH = MAX_LENGTH 140 | else 141 | READ_LENGTH = SIZE - index 142 | end 143 | if (index < SIZE) then 144 | hex_text = {} 145 | hex_values = {} 146 | text = io.read(file,index,READ_LENGTH) 147 | t = 1 148 | while (t <= 15) do 149 | if ((t*8) > string.len(text)) then 150 | temp = string.sub(text,1+(t-1)*8,-1) 151 | else 152 | temp = string.sub(text,1+(t-1)*8,t*8) 153 | end 154 | t2 = 1 155 | while t2 <= string.len(temp) do 156 | table.insert(hex_values,string.byte(temp,t2)) 157 | t2 = t2 + 1 158 | end 159 | table.insert(hex_text,temp) 160 | t = t + 1 161 | end 162 | table.insert(old_indexes, index) 163 | index = index + READ_LENGTH 164 | txt_i = txt_i + 1 165 | end 166 | return index 167 | end 168 | function PrintTop() 169 | Screen.clear(TOP_SCREEN) 170 | if (current_file == nil) then 171 | Font.print(ttf,0,0,"Basic Controls:",white,TOP_SCREEN) 172 | Font.print(ttf,0,15,"A = Open file/folder",white,TOP_SCREEN) 173 | Font.print(ttf,0,30,"X = Restore file/folder from SD card",white,TOP_SCREEN) 174 | Font.print(ttf,0,45,"Y = Dump file/folder to SD card",white,TOP_SCREEN) 175 | Font.print(ttf,0,60,"B = Return Main Menu",white,TOP_SCREEN) 176 | Font.print(ttf,0,75,"Left/Right = Scroll file",white,TOP_SCREEN) 177 | else 178 | for l, line in pairs(hex_text) do 179 | Font.print(ttf,280,(l-1)*15,string.gsub(line,"\0"," "),white,TOP_SCREEN) 180 | temp = 1 181 | while (temp <= string.len(line)) do 182 | if (temp % 2 == 0) then 183 | Font.print(ttf,0+(temp-1)*30,(l-1)*15,string.format('%02X', hex_values[(l-1)*8+temp]),white,TOP_SCREEN) 184 | else 185 | Font.print(ttf,0+(temp-1)*30,(l-1)*15,string.format('%02X', hex_values[(l-1)*8+temp]),red,TOP_SCREEN) 186 | end 187 | temp = temp + 1 188 | end 189 | end 190 | Font.print(ttf,0,225,"Offset: 0x" .. string.format('%X', old_indexes[#old_indexes]) .. " (" .. (old_indexes[#old_indexes]) .. ")",white,TOP_SCREEN) 191 | end 192 | end 193 | function PrintBottom() 194 | base_y = 0 195 | Screen.clear(BOTTOM_SCREEN) 196 | for l, file in pairs(files_table) do 197 | if (base_y > 226) then 198 | break 199 | end 200 | if (l >= master_index) then 201 | if (l==p) then 202 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 203 | color = selected_color 204 | else 205 | color = menu_color 206 | end 207 | if file.name == ".." then 208 | CropPrint(0,base_y,file.name,color,BOTTOM_SCREEN) 209 | else 210 | CropPrint(0,base_y,file.name.." ["..string.format('%02X',file.archive).."]",color,BOTTOM_SCREEN) 211 | end 212 | base_y = base_y + 15 213 | end 214 | end 215 | end 216 | 217 | -- Module main cycle 218 | function AppMainCycle() 219 | if update_top_screen then 220 | if (updateTXT) then 221 | txt_index = BuildHex(current_file,txt_index) 222 | updateTXT = false 223 | end 224 | OneshotPrint(PrintTop) 225 | update_top_screen = false 226 | end 227 | if update_bottom_screen then 228 | OneshotPrint(PrintBottom) 229 | update_bottom_screen = false 230 | end 231 | 232 | -- Base Controls Functions 233 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 234 | p = p - 1 235 | if (p >= 16) then 236 | master_index = p - 15 237 | end 238 | update_bottom_screen = true 239 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 240 | p = p + 1 241 | if (p >= 17) then 242 | master_index = p - 15 243 | end 244 | update_bottom_screen = true 245 | end 246 | if (p < 1) then 247 | p = #files_table 248 | if (p >= 17) then 249 | master_index = p - 15 250 | end 251 | elseif (p > #files_table) then 252 | master_index = 0 253 | p = 1 254 | end 255 | if (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 256 | if (files_table[p].directory) then 257 | OpenDirectory(files_table[p].name,files_table[p].archive) 258 | p=1 259 | master_index=0 260 | update_bottom_screen = true 261 | else 262 | OpenExtdataFile(files_table[p].name,files_table[p].archive) 263 | end 264 | elseif (Controls.check(pad,KEY_X)) and not (Controls.check(oldpad,KEY_X)) then 265 | if (files_table[p].directory) then 266 | RestoreFolder(files_table[p].name,files_table[p].archive) 267 | else 268 | RestoreFile(files_table[p].name,files_table[p].archive) 269 | end 270 | elseif (Controls.check(pad,KEY_Y)) and not (Controls.check(oldpad,KEY_Y)) then 271 | if (files_table[p].directory) then 272 | DumpFolder(files_table[p].name,files_table[p].archive) 273 | else 274 | DumpFile(files_table[p].name,files_table[p].archive) 275 | end 276 | elseif (Controls.check(pad,KEY_B)) and not (Controls.check(oldpad,KEY_B)) then 277 | ExtGC() 278 | CallMainMenu() 279 | elseif (Controls.check(pad,KEY_DLEFT)) and not (Controls.check(oldpad,KEY_DLEFT)) then 280 | if current_file ~= nil then 281 | update_top_screen = true 282 | if (txt_i > 1) then 283 | updateTXT = true 284 | table.remove(old_indexes) 285 | txt_index = table.remove(old_indexes) 286 | txt_i = txt_i - 2 287 | end 288 | end 289 | elseif (Controls.check(pad,KEY_DRIGHT)) and not (Controls.check(oldpad,KEY_DRIGHT)) then 290 | if current_file ~= nil then 291 | update_top_screen = true 292 | updateTXT = true 293 | end 294 | elseif (Controls.check(pad,KEY_TOUCH)) then 295 | x,y = Controls.readTouch() 296 | new_index = math.ceil(y/15) 297 | if (new_index <= #files_table) then 298 | if master_index > 0 then 299 | p = new_index + master_index - 1 300 | else 301 | p = new_index 302 | end 303 | end 304 | end 305 | if not (Controls.check(pad,KEY_TOUCH)) then 306 | master_index = p - 15 307 | end 308 | end 309 | -------------------------------------------------------------------------------- /LUA/modules/video.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Video" mode 2 | mode = "Video" 3 | 4 | -- Internal module settings 5 | master_index_v = 0 6 | if Screen.get3DLevel() > 0 then 7 | Screen.enable3D() 8 | end 9 | old_3d = Screen.get3DLevel() 10 | p_v = 1 11 | update_frame = false 12 | ui_enabled = false 13 | DisableRenderer() 14 | not_started_v = true 15 | tmp = System.listDirectory("/VIDEO") 16 | my_videos = {} 17 | hide = false 18 | for i,file in pairs(tmp) do 19 | if not file.directory then 20 | tmp_file = io.open("/VIDEO/"..file.name,FREAD) 21 | magic = io.read(tmp_file,0,4) 22 | if magic == "BMPV" then 23 | table.insert(my_videos,{file.name,"BMPV"}) 24 | elseif magic == "JPGV" then 25 | table.insert(my_videos,{file.name,"JPGV"}) 26 | end 27 | io.close(tmp_file) 28 | end 29 | end 30 | if #my_videos > 0 then 31 | current_type = my_videos[1][2] 32 | if current_type == "BMPV" then 33 | current_file = BMPV.load("/VIDEO/"..my_videos[1][1]) 34 | current_size = BMPV.getSize(current_file) 35 | elseif current_type == "JPGV" then 36 | current_file = JPGV.load("/VIDEO/"..my_videos[1][1]) 37 | current_size = JPGV.getSize(current_file) 38 | end 39 | else 40 | ShowError("VIDEO folder is empty.") 41 | CallMainMenu() 42 | end 43 | 44 | frame_succession = Timer.new() 45 | current_frame = 60 46 | 47 | -- Rendering functions 48 | function AppTopScreenRender() 49 | Graphics.fillRect(5,395,40,220,black) 50 | Graphics.fillRect(6,394,41,219,white) 51 | end 52 | 53 | function AppBottomScreenRender() 54 | end 55 | 56 | -- Module main cycle 57 | function AppMainCycle() 58 | slide_status = Screen.get3DLevel() 59 | 60 | -- Clear bottom screen 61 | Screen.clear(BOTTOM_SCREEN) 62 | if not_started_v then 63 | 64 | -- Update preview info 65 | if update_frame then 66 | Timer.reset(frame_succession) 67 | update_frame = false 68 | current_type = my_videos[p_v][2] 69 | if current_type == "BMPV" then 70 | current_file = BMPV.load("/VIDEO/"..my_videos[p_v][1]) 71 | current_size = BMPV.getSize(current_file) 72 | elseif current_type == "JPGV" then 73 | current_file = JPGV.load("/VIDEO/"..my_videos[p_v][1]) 74 | current_size = JPGV.getSize(current_file) 75 | end 76 | current_frame = 60 77 | Timer.reset(frame_succession) 78 | end 79 | 80 | -- Showing Preview frames 81 | if current_type == "BMPV" then 82 | BMPV.showFrame(0,0,current_file,current_frame,TOP_SCREEN) 83 | elseif current_type == "JPGV" then 84 | if (slide_status == 0) then 85 | JPGV.showFrame(0,0,current_file,current_frame,TOP_SCREEN,false) 86 | else 87 | JPGV.showFrame(0,0,current_file,current_frame,TOP_SCREEN,true) 88 | end 89 | end 90 | if Timer.getTime(frame_succession) > 5000 then 91 | current_frame = math.ceil(current_frame + (current_size / 10)) 92 | if current_frame > current_size then 93 | current_frame = 1 94 | end 95 | Timer.reset(frame_succession) 96 | end 97 | 98 | -- Showing files list 99 | base_y = 0 100 | for l, file in pairs(my_videos) do 101 | if (base_y > 226) then 102 | break 103 | end 104 | if (l >= master_index_v) then 105 | if (l==p_v) then 106 | base_y2 = base_y 107 | if (base_y) == 0 then 108 | base_y = 2 109 | end 110 | Screen.fillRect(0,319,base_y-2,base_y2+12,selected_item,BOTTOM_SCREEN) 111 | color = selected 112 | if (base_y) == 2 then 113 | base_y = 0 114 | end 115 | else 116 | color = white 117 | end 118 | DebugCropPrint(0,base_y,file[1],color,BOTTOM_SCREEN) 119 | base_y = base_y + 15 120 | end 121 | end 122 | 123 | else 124 | 125 | -- Video playback 126 | if current_type == "JPGV" then 127 | if (slide_status == 0) then 128 | JPGV.draw(0,0,current_file,TOP_SCREEN,false) 129 | else 130 | JPGV.draw(0,0,current_file,TOP_SCREEN,true) 131 | end 132 | else 133 | BMPV.draw(0,0,current_file,TOP_SCREEN) 134 | end 135 | 136 | if not hide then 137 | -- Video playback info (JPGV side) 138 | if current_type == "JPGV" then 139 | Screen.debugPrint(0,10,"A = Pause/Resume",white,BOTTOM_SCREEN) 140 | Screen.debugPrint(0,25,"Y = Close video",white,BOTTOM_SCREEN) 141 | Screen.debugPrint(0,40,"B = Return Main Menu",white,BOTTOM_SCREEN) 142 | Screen.debugPrint(0,55,"X = Hide Bottom Screen",white,BOTTOM_SCREEN) 143 | Screen.debugPrint(0,100,"Infos:",white,BOTTOM_SCREEN) 144 | Screen.debugPrint(0,114,"FPS: "..JPGV.getFPS(current_file),white,BOTTOM_SCREEN) 145 | cur_time_sec = math.ceil(JPGV.getFrame(current_file) / JPGV.getFPS(current_file)) 146 | cur_time_min = 0 147 | while (cur_time_sec >= 60) do 148 | cur_time_sec = cur_time_sec - 60 149 | cur_time_min = cur_time_min + 1 150 | end 151 | if (cur_time_sec < 10) then 152 | Screen.debugPrint(0,128,"Time: " .. cur_time_min .. ":0" .. cur_time_sec .. " / " .. tot_time_min .. ":" .. tot_time_sec,white,BOTTOM_SCREEN) 153 | else 154 | Screen.debugPrint(0,128,"Time: " .. cur_time_min .. ":" .. cur_time_sec .. " / " .. tot_time_min .. ":" .. tot_time_sec,white,BOTTOM_SCREEN) 155 | end 156 | Screen.debugPrint(0,142,"Samplerate: "..JPGV.getSrate(current_file),white,BOTTOM_SCREEN) 157 | percentage = ((JPGV.getFrame(current_file) * 100) / JPGV.getSize(current_file)) 158 | Screen.debugPrint(0,200,"Percentage: " ..math.ceil(percentage) .. "%",white,BOTTOM_SCREEN) 159 | Screen.fillEmptyRect(2,318,214,234,white,BOTTOM_SCREEN) 160 | move = ((314 * percentage) / 100) 161 | Screen.fillRect(3,3 + math.ceil(move),215,233,white,BOTTOM_SCREEN) 162 | 163 | -- Video playback info (BMPV side) 164 | elseif current_type == "BMPV" then 165 | Screen.debugPrint(0,10,"A = Pause/Resume",white,BOTTOM_SCREEN) 166 | Screen.debugPrint(0,25,"Y = Close video",white,BOTTOM_SCREEN) 167 | Screen.debugPrint(0,40,"B = Return Main Menu",white,BOTTOM_SCREEN) 168 | Screen.debugPrint(0,55,"X = Power off Bottom Screen",white,BOTTOM_SCREEN) 169 | Screen.debugPrint(0,100,"Infos:",white,BOTTOM_SCREEN) 170 | Screen.debugPrint(0,114,"FPS: "..BMPV.getFPS(current_file),white,BOTTOM_SCREEN) 171 | cur_time_sec = math.ceil(BMPV.getFrame(current_file) / BMPV.getFPS(current_file)) 172 | cur_time_min = 0 173 | while (cur_time_sec >= 60) do 174 | cur_time_sec = cur_time_sec - 60 175 | cur_time_min = cur_time_min + 1 176 | end 177 | if (cur_time_sec < 10) then 178 | Screen.debugPrint(0,128,"Time: " .. cur_time_min .. ":0" .. cur_time_sec .. " / " .. tot_time_min .. ":" .. tot_time_sec,white,BOTTOM_SCREEN) 179 | else 180 | Screen.debugPrint(0,128,"Time: " .. cur_time_min .. ":" .. cur_time_sec .. " / " .. tot_time_min .. ":" .. tot_time_sec,white,BOTTOM_SCREEN) 181 | end 182 | Screen.debugPrint(0,142,"Samplerate: "..BMPV.getSrate(current_file),white,BOTTOM_SCREEN) 183 | percentage = math.ceil((BMPV.getFrame(current_file) * 100) / BMPV.getSize(current_file)) 184 | Screen.debugPrint(0,200,"Percentage: " ..percentage .. "%",white,BOTTOM_SCREEN) 185 | Screen.fillEmptyRect(2,318,214,234,white,BOTTOM_SCREEN) 186 | move = ((314 * percentage) / 100) 187 | Screen.fillRect(3,3 + math.ceil(move),215,233,white,BOTTOM_SCREEN) 188 | end 189 | end 190 | 191 | end 192 | 193 | -- 3D effect support 194 | if slide_status == 0 and old_3d ~= 0 then 195 | Screen.disable3D() 196 | elseif slide_status > 0 and old_3d == 0 then 197 | Screen.enable3D() 198 | end 199 | 200 | -- Sets controls triggering 201 | if (Controls.check(pad,KEY_Y)) and not (Controls.check(oldpad,KEY_Y)) and not (not_started_v) then 202 | Timer.reset(frame_succession) 203 | not_started_v = true 204 | if hide then 205 | Controls.enableScreen(BOTTOM_SCREEN) 206 | hide = false 207 | end 208 | if current_type == "JPGV" then 209 | JPGV.stop(current_file) 210 | else 211 | BMPV.stop(current_file) 212 | end 213 | elseif (Controls.check(pad,KEY_X)) and not (Controls.check(oldpad,KEY_X)) and not (not_started_v) then 214 | if hide then 215 | Controls.enableScreen(BOTTOM_SCREEN) 216 | else 217 | Controls.disableScreen(BOTTOM_SCREEN) 218 | end 219 | hide = not hide 220 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 221 | if not_started_v then 222 | not_started_v = false 223 | if current_type == "JPGV" then 224 | JPGV.start(current_file,NO_LOOP,0x08,0x09) 225 | tot_time_sec = math.ceil(JPGV.getSize(current_file) / JPGV.getFPS(current_file)) 226 | tot_time_min = 0 227 | while (tot_time_sec >= 60) do 228 | tot_time_sec = tot_time_sec - 60 229 | tot_time_min = tot_time_min + 1 230 | end 231 | elseif current_type == "BMPV" then 232 | BMPV.start(current_file,NO_LOOP,0x08,0x09) 233 | tot_time_sec = math.ceil(BMPV.getSize(current_file) / BMPV.getFPS(current_file)) 234 | tot_time_min = 0 235 | while (tot_time_sec >= 60) do 236 | tot_time_sec = tot_time_sec - 60 237 | tot_time_min = tot_time_min + 1 238 | end 239 | end 240 | else 241 | if current_type == "JPGV" then 242 | if JPGV.isPlaying(current_file) then 243 | JPGV.pause(current_file) 244 | else 245 | JPGV.resume(current_file) 246 | end 247 | else 248 | if BMPV.isPlaying(current_file) then 249 | BMPV.pause(current_file) 250 | else 251 | BMPV.resume(current_file) 252 | end 253 | end 254 | end 255 | elseif Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 256 | if (slide_status > 0) then 257 | Screen.disable3D() 258 | end 259 | if hide then 260 | Controls.enableScreen(BOTTOM_SCREEN) 261 | end 262 | CallMainMenu() 263 | Timer.destroy(frame_succession) 264 | if current_type == "JPGV" then 265 | JPGV.stop(current_file) 266 | JPGV.unload(current_file) 267 | elseif current_type == "BMPV" then 268 | BMPV.stop(current_file) 269 | BMPV.unload(current_file) 270 | end 271 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) and not_started_v then 272 | if current_type == "JPGV" then 273 | JPGV.unload(current_file) 274 | elseif current_type == "BMPV" then 275 | BMPV.unload(current_file) 276 | end 277 | p_v = p_v - 1 278 | if (p_v >= 16) then 279 | master_index_v = p_v - 15 280 | end 281 | update_frame = true 282 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) and not_started_v then 283 | if current_type == "JPGV" then 284 | JPGV.unload(current_file) 285 | elseif current_type == "BMPV" then 286 | BMPV.unload(current_file) 287 | end 288 | p_v = p_v + 1 289 | if (p_v >= 17) then 290 | master_index_v = p_v - 15 291 | end 292 | update_frame = true 293 | end 294 | if (p_v < 1) then 295 | p_v = #my_videos 296 | if (p_v >= 17) then 297 | master_index_v = p_v - 15 298 | end 299 | elseif (p_v > #my_videos) then 300 | master_index_v = 0 301 | p_v = 1 302 | end 303 | old_3d = slide_status 304 | end -------------------------------------------------------------------------------- /LUA/sun_index.lua: -------------------------------------------------------------------------------- 1 | -- Create system folders if doesn't exist 2 | System.createDirectory("/VIDEO") 3 | System.createDirectory("/MUSIC") 4 | System.createDirectory("/DCIM") 5 | 6 | -- Open config file and system files 7 | dofile("/config.sun") 8 | theme_dir = main_dir.."/themes/"..theme 9 | Graphics.init() 10 | bg = Graphics.loadImage(theme_dir.."/images/bg.jpg") 11 | if System.doesFileExist(theme_dir.."/images/music.jpg") then 12 | ext = ".jpg" 13 | else 14 | ext = ".png" 15 | end 16 | music = Graphics.loadImage(theme_dir.."/images/music"..ext) 17 | video = Graphics.loadImage(theme_dir.."/images/video"..ext) 18 | info = Graphics.loadImage(theme_dir.."/images/info"..ext) 19 | fb = Graphics.loadImage(theme_dir.."/images/fb"..ext) 20 | game = Graphics.loadImage(theme_dir.."/images/game"..ext) 21 | camera = Graphics.loadImage(theme_dir.."/images/camera"..ext) 22 | gallery = Graphics.loadImage(theme_dir.."/images/gallery"..ext) 23 | cia = Graphics.loadImage(theme_dir.."/images/cia"..ext) 24 | extdata = Graphics.loadImage(theme_dir.."/images/extdata"..ext) 25 | calc = Graphics.loadImage(theme_dir.."/images/calc"..ext) 26 | mail = Graphics.loadImage(theme_dir.."/images/mail"..ext) 27 | themes = Graphics.loadImage(theme_dir.."/images/themes"..ext) 28 | clock = Graphics.loadImage(theme_dir.."/images/clock"..ext) 29 | ftp = Graphics.loadImage(theme_dir.."/images/ftp"..ext) 30 | charge = Graphics.loadImage(theme_dir.."/images/charge"..ext) 31 | record = Graphics.loadImage(theme_dir.."/images/record"..ext) 32 | b0 = Graphics.loadImage(theme_dir.."/images/0"..ext) 33 | b1 = Graphics.loadImage(theme_dir.."/images/1"..ext) 34 | b2 = Graphics.loadImage(theme_dir.."/images/2"..ext) 35 | b3 = Graphics.loadImage(theme_dir.."/images/3"..ext) 36 | b4 = Graphics.loadImage(theme_dir.."/images/4"..ext) 37 | b5 = Graphics.loadImage(theme_dir.."/images/5"..ext) 38 | ttf = Font.load(theme_dir.."/fonts/main.ttf") 39 | dofile(theme_dir.."/colors.lua") 40 | Font.setPixelSizes(ttf,18) 41 | 42 | -- Setting some system vars, funcs, etc... 43 | bg_apps = {} 44 | topbar_icons = {} 45 | old_headset = Controls.headsetStatus() 46 | in_game = false 47 | renderer = true 48 | refresh_screen = true 49 | refresh_screen2 = true 50 | Sound.init() 51 | app_index = 1 52 | version = "0.5 PRO" 53 | ui_enabled = true 54 | screenshots = true 55 | oldpad = KEY_A 56 | module = "Main Menu" 57 | months = {"January", "February","March","April","May","June","July", "August", "September", "October", "November", "December"} 58 | days_table = {} 59 | dv,d,m,y = System.getDate() 60 | if (y % 400 == 0) or (y % 100 ~= 0 and y % 4 == 0) then 61 | month_days = {31,29,31,30,31,30,31,31,30,31,30,31} 62 | else 63 | month_days = {31,28,31,30,31,30,31,31,30,31,30,31} 64 | end 65 | i = 1 66 | if i == d then 67 | if dv == 7 then 68 | dv = 0 69 | end 70 | table.insert(days_table,dv) 71 | else 72 | tmp = ((d - i) % 7) 73 | if tmp > dv then 74 | my_dv = 7 + dv - tmp 75 | else 76 | my_dv = dv - tmp 77 | end 78 | if my_dv > 6 then 79 | my_dv = 0 80 | end 81 | table.insert(days_table,my_dv) 82 | end 83 | i = 2 84 | while i <= month_days[m] do 85 | my_dv = days_table[i-1] + 1 86 | if my_dv > 6 then 87 | my_dv = 0 88 | end 89 | table.insert(days_table,my_dv) 90 | i=i+1 91 | end 92 | -- Loading internal extra Sunshell functions 93 | dofile(main_dir.."/scripts/funcs.lua") 94 | 95 | -- Set detected build in use 96 | build_idx = System.checkBuild() 97 | if build_idx == 0 then 98 | build = "Ninjhax 1" 99 | elseif build_idx == 1 then 100 | build = "Custom Firmware" 101 | else 102 | build = "Ninjhax 2" 103 | end 104 | 105 | -- Setting modules as apps 106 | tools = {} 107 | table.insert(tools,{game,"/modules/game.lua","Applications"}) 108 | table.insert(tools,{info,"/modules/info.lua","Console Info"}) 109 | table.insert(tools,{camera,"/modules/camera.lua","Camera"}) 110 | table.insert(tools,{gallery,"/modules/gallery.lua","Gallery"}) 111 | table.insert(tools,{music,"/modules/music.lua","Musics"}) 112 | table.insert(tools,{video,"/modules/video.lua","Videos"}) 113 | table.insert(tools,{fb,"/modules/fb.lua","Filebrowser"}) 114 | table.insert(tools,{cia,"/modules/cia.lua","CIA Manager"}) 115 | table.insert(tools,{extdata,"/modules/extdata.lua","Extdata Manager"}) 116 | table.insert(tools,{calc,"/modules/calc.lua","Calc"}) 117 | table.insert(tools,{record,"/modules/record.lua","Record"}) 118 | table.insert(tools,{clock,"/modules/clock.lua","Clock"}) 119 | table.insert(tools,{ftp,"/modules/ftp.lua","FTP Server"}) 120 | table.insert(tools,{mail,"/modules/mail.lua","Mail"}) 121 | table.insert(tools,{themes,"/modules/themes.lua","Theme Manager"}) 122 | 123 | -- Top screen rendering 124 | function drawTopScreenUI() 125 | 126 | -- Background 127 | if ui_enabled then 128 | Graphics.drawPartialImage(0,0,0,0,400,240,bg) 129 | end 130 | 131 | -- Calendar 132 | if mode == nil then 133 | Graphics.fillRect(75,320,40,200,black) 134 | Graphics.fillRect(76,319,41,199,white) 135 | end 136 | 137 | if ui_enabled then 138 | 139 | -- Topbar icons 140 | for i,icon in pairs(topbar_icons) do 141 | Graphics.drawImage(350-i*21,2,icon[1],TOP_SCREEN) 142 | end 143 | 144 | -- Battery and Wifi alert 145 | if System.isBatteryCharging() then 146 | Graphics.drawImage(350,2,charge) 147 | else 148 | battery_lv = System.getBatteryLife() 149 | if battery_lv == 0 then 150 | Graphics.drawImage(350,2,b0) 151 | elseif battery_lv == 1 then 152 | Graphics.drawImage(350,2,b1) 153 | elseif battery_lv == 2 then 154 | Graphics.drawImage(350,2,b2) 155 | elseif battery_lv == 3 then 156 | Graphics.drawImage(350,2,b3) 157 | elseif battery_lv == 4 then 158 | Graphics.drawImage(350,2,b4) 159 | else 160 | Graphics.drawImage(350,2,b5) 161 | end 162 | end 163 | if Network.isWifiEnabled() then 164 | net_lv = Network.getWifiLevel() 165 | if net_lv > 0 then 166 | Graphics.fillRect(326-#topbar_icons*21,331-#topbar_icons*21,14,18,green_wifi) 167 | if net_lv > 1 then 168 | Graphics.fillRect(333-#topbar_icons*21,338-#topbar_icons*21,8,18,green_wifi) 169 | if net_lv > 2 then 170 | Graphics.fillRect(340-#topbar_icons*21,345-#topbar_icons*21,2,18,green_wifi) 171 | end 172 | end 173 | end 174 | else 175 | Graphics.fillRect(340-#topbar_icons*21,345-#topbar_icons*21,16,18,red_wifi) 176 | Graphics.fillRect(333-#topbar_icons*21,338-#topbar_icons*21,16,18,red_wifi) 177 | Graphics.fillRect(326-#topbar_icons*21,331-#topbar_icons*21,16,18,red_wifi) 178 | end 179 | end 180 | 181 | end 182 | 183 | function drawBottomScreenUI() 184 | if ui_enabled then 185 | if refresh_screen then 186 | Graphics.drawPartialImage(0,0,40,240,320,240,bg) 187 | end 188 | end 189 | if mode == nil then 190 | x = 4 191 | y = 10 192 | for i,tool in pairs(tools) do 193 | if x > 300 then 194 | x = 4 195 | y = y + 58 196 | end 197 | if app_index == i then 198 | Graphics.fillRect(x-3,x+50,y-3,y+50,selected) 199 | end 200 | Graphics.drawImage(x,y,tool[1]) 201 | if Controls.check(pad,KEY_TOUCH) and not Controls.check(oldpad,KEY_TOUCH) then 202 | tx,ty = Controls.readTouch() 203 | if tx >= x and tx <= x+48 and ty >= y and ty <= y+48 then 204 | module = tool[3] 205 | dofile(main_dir..tool[2]) 206 | end 207 | end 208 | x = x + 53 209 | end 210 | end 211 | end 212 | 213 | -- Main cycle 214 | while true do 215 | Screen.refresh() 216 | pad = Controls.read() 217 | 218 | -- Headset tracking for Music module auto-start 219 | if old_headset and not Controls.headsetStatus() then -- Removing headset 220 | for i,app in pairs(bg_apps) do 221 | if app[3] == "Music" then 222 | if Sound.isPlaying(current_song) then 223 | Sound.pause(current_song) 224 | end 225 | break 226 | end 227 | end 228 | old_headset = false 229 | elseif not old_headset and Controls.headsetStatus() then -- Inserting headset 230 | done = false 231 | for i,app in pairs(bg_apps) do 232 | if app[3] == "Music" then 233 | if not Sound.isPlaying(current_song) then 234 | Sound.resume(current_song) 235 | end 236 | done = true 237 | break 238 | end 239 | end 240 | if not done then 241 | dofile(main_dir.."/scripts/music_api.lua") 242 | end 243 | old_headset = true 244 | end 245 | 246 | if renderer then 247 | 248 | -- Top screen rendering 249 | if refresh_screen2 then 250 | Graphics.initBlend(TOP_SCREEN) 251 | drawTopScreenUI() 252 | if mode ~= nil then 253 | AppTopScreenRender() 254 | end 255 | Graphics.termBlend() 256 | end 257 | 258 | -- Bottom screen rendering 259 | if refresh_screen then 260 | Graphics.initBlend(BOTTOM_SCREEN) 261 | drawBottomScreenUI() 262 | if mode ~= nil then 263 | AppBottomScreenRender() 264 | end 265 | Graphics.termBlend() 266 | end 267 | 268 | end 269 | 270 | -- Executing background apps 271 | for i,bg_app_code in pairs(bg_apps) do 272 | bg_app_code[1]() 273 | end 274 | 275 | -- Main menu 276 | if mode == nil then 277 | if widget == nil then 278 | 279 | -- Blit calendar 280 | dv,d,m,ye = System.getDate() 281 | i = 1 282 | x = 80 283 | y = 85 284 | Font.print(ttf,x+85,y-40,months[m].." "..ye,black,TOP_SCREEN) 285 | Font.print(ttf,x,y-20,"S",selected,TOP_SCREEN) 286 | Font.print(ttf,x+35,y-20,"M",selected,TOP_SCREEN) 287 | Font.print(ttf,x+70,y-20,"T",selected,TOP_SCREEN) 288 | Font.print(ttf,x+105,y-20,"W",selected,TOP_SCREEN) 289 | Font.print(ttf,x+140,y-20,"T",selected,TOP_SCREEN) 290 | Font.print(ttf,x+175,y-20,"F",selected,TOP_SCREEN) 291 | Font.print(ttf,x+210,y-20,"S",selected,TOP_SCREEN) 292 | while i <= month_days[m] do 293 | if i == d then 294 | Font.print(ttf,x + (days_table[i]) * 35,y,i,selected,TOP_SCREEN) 295 | else 296 | Font.print(ttf,x + (days_table[i]) * 35,y,i,black,TOP_SCREEN) 297 | end 298 | if days_table[i] == 6 then 299 | y = y + 20 300 | end 301 | i=i+1 302 | end 303 | 304 | end 305 | 306 | -- Setting digital pad controls triggering 307 | if Controls.check(pad,KEY_DUP) and not Controls.check(oldpad,KEY_DUP) then 308 | app_index = app_index - 6 309 | if app_index < 1 then 310 | while (app_index + 6) <= #tools do 311 | app_index = app_index + 6 312 | end 313 | end 314 | elseif Controls.check(pad,KEY_DDOWN) and not Controls.check(oldpad,KEY_DDOWN) then 315 | app_index = app_index + 6 316 | if app_index > #tools then 317 | app_index = app_index % 6 318 | if app_index == 0 then 319 | app_index = 6 320 | end 321 | end 322 | elseif Controls.check(pad,KEY_DLEFT) and not Controls.check(oldpad,KEY_DLEFT) then 323 | app_index = app_index - 1 324 | if app_index % 6 == 0 then 325 | app_index = app_index + 6 326 | if app_index > #tools then 327 | app_index = #tools 328 | end 329 | end 330 | elseif Controls.check(pad,KEY_DRIGHT) and not Controls.check(oldpad,KEY_DRIGHT) then 331 | app_index = app_index + 1 332 | if app_index % 6 == 1 then 333 | app_index = app_index - 6 334 | end 335 | if app_index > #tools then 336 | app_index = math.floor(app_index / 6) * 6 + 1 337 | end 338 | end 339 | 340 | -- Setting app starting by pressing A button 341 | if Controls.check(pad,KEY_A) and not Controls.check(oldpad,KEY_A) then 342 | module = tools[app_index][3] 343 | dofile(main_dir..tools[app_index][2]) 344 | end 345 | else 346 | 347 | -- App cycles 348 | AppMainCycle() 349 | 350 | end 351 | 352 | -- Blit topbar info 353 | if ui_enabled then 354 | 355 | -- Blit clock 356 | hours,minutes,seconds = System.getTime() 357 | if minutes < 10 then 358 | minutes = "0"..minutes 359 | end 360 | if seconds < 10 then 361 | seconds = "0"..seconds 362 | end 363 | formatted_time = hours..":"..minutes..":"..seconds 364 | Font.print(ttf,276-#topbar_icons*21,3,formatted_time,white,TOP_SCREEN) 365 | Font.print(ttf,4,3,"Sunshell v."..version.." - "..module,white,TOP_SCREEN) 366 | 367 | end 368 | 369 | -- Sets up universal controls 370 | if Controls.check(pad,KEY_START) and not in_game and not Controls.check(oldpad,KEY_START) then 371 | GarbageCollection() 372 | for i,bg_apps_code in pairs(bg_apps) do 373 | bg_apps_code[2]() 374 | end 375 | for i,icon in pairs(topbar_icons) do 376 | Graphics.freeImage(icon[1]) 377 | end 378 | Sound.term() 379 | Font.unload(ttf) 380 | Graphics.term() 381 | if start_dir == "/" and build ~= "Custom Firmware" then -- boot.3dsx patch 382 | System.reboot() 383 | else 384 | System.exit() 385 | end 386 | elseif Controls.check(pad,KEY_L) and not Controls.check(oldpad,KEY_L) and screenshots then 387 | screen_index = 0 388 | while System.doesFileExist("/DCIM/Sunshell_"..screen_index..".jpg") do 389 | screen_index = screen_index + 1 390 | end 391 | System.takeScreenshot("/DCIM/Sunshell_"..screen_index..".jpg",true) 392 | end 393 | 394 | Screen.waitVblankStart() 395 | Screen.flip() 396 | oldpad = pad 397 | if in_game then 398 | in_game = false 399 | end 400 | end -------------------------------------------------------------------------------- /LUA/scripts/funcs.lua: -------------------------------------------------------------------------------- 1 | -- Internal SunShell variables 2 | -- ui_enabled = true/false -- Sets Sunshell UI state (CallMainMenu force automatically ui_enabled to true value) 3 | -- screenshots = true/false -- Sets Sunshell screenshot function through L button state (CallMainMenu force automatically screenshots to true value) 4 | 5 | -- Internal SunShell extra functions 6 | 7 | start_dir = System.currentDirectory() 8 | 9 | -- * explode 10 | -- PHP explode porting for LUA developing 11 | function explode(div,str) 12 | pos = 0 13 | arr = {} 14 | for st,sp in function() return string.find(str,div,pos,true) end do 15 | table.insert(arr,string.sub(str,pos,st-1)) 16 | pos = sp + 1 17 | end 18 | table.insert(arr,string.sub(str,pos)) 19 | return arr 20 | end 21 | 22 | -- * CallMainMenu 23 | -- Sets SunShell to Main Menu mode, usefull to exit from a module 24 | function CallMainMenu() 25 | mode = nil 26 | module = "Main Menu" 27 | ui_enabled = true 28 | renderer = true 29 | screenshots = true 30 | refresh_screen = true 31 | refresh_screen2 = true 32 | System.currentDirectory(start_dir) 33 | end 34 | 35 | -- * SetBottomRefresh 36 | -- Sets Refreshing Screen state for Bottom Screen 37 | function SetBottomRefresh(value) 38 | refresh_screen = value 39 | end 40 | 41 | -- * SetTopRefresh 42 | -- Sets Refreshing Screen state for Top Screen 43 | function SetTopRefresh(value) 44 | refresh_screen2 = value 45 | end 46 | 47 | -- * DisableRenderer 48 | -- Disable GPU renderer for both screens 49 | function DisableRenderer() 50 | renderer = false 51 | end 52 | 53 | -- * CustomRenderBottom 54 | -- Sets up a custom GPU rendering scene for bottom screen 55 | function CustomRenderBottom(func) 56 | Graphics.initBlend(BOTTOM_SCREEN) 57 | func() 58 | Graphics.termBlend() 59 | end 60 | 61 | -- * CustomRenderTop 62 | -- Sets up a custom GPU rendering scene for top screen 63 | function CustomRenderTop(func) 64 | Graphics.initBlend(TOP_SCREEN) 65 | func() 66 | Graphics.termBlend() 67 | end 68 | 69 | -- * CloseBGApp 70 | -- Close a selected BG App 71 | function CloseBGApp(my_app) 72 | for i, apps in pairs(bg_apps) do 73 | if apps[3] == my_app then 74 | apps[2]() 75 | table.remove(bg_apps,i) 76 | break 77 | end 78 | end 79 | end 80 | 81 | -- * FormatTime 82 | -- Format a number of seconds in a time-like string (Example: 123 seconds = 02:03) 83 | function FormatTime(seconds) 84 | minute = math.floor(seconds/60) 85 | seconds = seconds%60 86 | hours = math.floor(minute/60) 87 | minute = minute%60 88 | if minute < 10 then 89 | minute = "0"..minute 90 | end 91 | if seconds < 10 then 92 | seconds = "0"..seconds 93 | end 94 | if hours == 0 then 95 | return minute..":"..seconds 96 | else 97 | return hours..":"..minute..":"..seconds 98 | end 99 | end 100 | 101 | -- * GarbageCollection 102 | -- Free all allocated SunShell elements 103 | function GarbageCollection() 104 | Graphics.freeImage(bg) 105 | Graphics.freeImage(b0) 106 | Graphics.freeImage(b1) 107 | Graphics.freeImage(b2) 108 | Graphics.freeImage(b3) 109 | Graphics.freeImage(b4) 110 | Graphics.freeImage(b5) 111 | Graphics.freeImage(charge) 112 | for i,tool in pairs(tools) do 113 | Graphics.freeImage(tool[1]) 114 | end 115 | end 116 | 117 | -- * CropPrint 118 | -- Used to print long strings on BOTTOM_SCREEN, it automatically crop too long strings 119 | function CropPrint(x, y, text, color, screen) 120 | if string.len(text) > 50 then 121 | Font.print(ttf,x+2, y, string.sub(text,1,50) .. "...", color, screen) 122 | else 123 | Font.print(ttf,x+2, y, text, color, screen) 124 | end 125 | end 126 | 127 | -- * TopCropPrint 128 | -- Used to print long strings on TOP_SCREEN, it automatically crop too long strings 129 | function TopCropPrint(x, y, text, color, screen) 130 | if string.len(text) > 100 then 131 | Font.print(ttf,x+2, y, string.sub(text,1,100) .. "...", color, screen) 132 | else 133 | Font.print(ttf,x+2, y, text, color, screen) 134 | end 135 | end 136 | 137 | -- * DebugCropPrint 138 | -- Used to print long strings on BOTTOM_SCREEN, it automatically crop too long strings 139 | function DebugCropPrint(x, y, text, color, screen) 140 | if string.len(text) > 25 then 141 | Screen.debugPrint(x, y, string.sub(text,1,25) .. "...", color, screen) 142 | else 143 | Screen.debugPrint(x, y, text, color, screen) 144 | end 145 | end 146 | 147 | -- * DebugTopCropPrint 148 | -- Used to print long strings on TOP_SCREEN, it automatically crop too long strings 149 | function DebugTopCropPrint(x, y, text, color, screen) 150 | if string.len(text) > 42 then 151 | Screen.debugPrint(x, y, string.sub(text,1,42) .. "...", color, screen) 152 | else 153 | Screen.debugPrint(x, y, text, color, screen) 154 | end 155 | end 156 | 157 | -- * LastSpace 158 | -- Return index of last space for text argument 159 | function LastSpace(text) 160 | found = false 161 | start = -1 162 | while string.sub(text,start,start) ~= " " do 163 | start = start - 1 164 | end 165 | return start 166 | end 167 | 168 | -- * ErrorGenerator 169 | -- PRIVATE FUNCTION: DO NOT USE 170 | function ErrorGenerator(text) 171 | y = 68 172 | error_lines = {} 173 | while string.len(text) > 50 do 174 | endl = 51 + LastSpace(string.sub(text,1,50)) 175 | table.insert(error_lines,{string.sub(text,1,endl), y}) 176 | text = string.sub(text,endl+1,-1) 177 | y = y + 15 178 | end 179 | if string.len(text) > 0 then 180 | table.insert(error_lines,{text, y}) 181 | end 182 | end 183 | 184 | -- * ShowError 185 | -- Shows a SunShell error with a customizable text 186 | function ShowError(text) 187 | confirm = false 188 | ErrorGenerator(text) 189 | max_y = error_lines[#error_lines][2] + 40 190 | Screen.fillEmptyRect(5,315,50,max_y,black,BOTTOM_SCREEN) 191 | Screen.fillRect(6,314,51,max_y-1,white,BOTTOM_SCREEN) 192 | Font.print(ttf,8,53,"Error",selected,BOTTOM_SCREEN) 193 | for i,line in pairs(error_lines) do 194 | Font.print(ttf,8,line[2],line[1],black,BOTTOM_SCREEN) 195 | end 196 | Screen.fillEmptyRect(147,176,max_y - 23, max_y - 8,black,BOTTOM_SCREEN) 197 | Font.print(ttf,155,max_y - 23,"OK",black,BOTTOM_SCREEN) 198 | Screen.flip() 199 | Screen.refresh() 200 | Screen.fillEmptyRect(5,315,50,max_y,black,BOTTOM_SCREEN) 201 | Screen.fillRect(6,314,51,max_y-1,white,BOTTOM_SCREEN) 202 | Font.print(ttf,8,53,"Error",selected,BOTTOM_SCREEN) 203 | for i,line in pairs(error_lines) do 204 | Font.print(ttf,8,line[2],line[1],black,BOTTOM_SCREEN) 205 | end 206 | Screen.fillEmptyRect(147,176,max_y - 23, max_y - 8,black,BOTTOM_SCREEN) 207 | Font.print(ttf,155,max_y - 23,"OK",black,BOTTOM_SCREEN) 208 | while not confirm do 209 | if (Controls.check(Controls.read(),KEY_TOUCH)) then 210 | x,y = Controls.readTouch() 211 | if x >= 147 and x <= 176 and y >= max_y - 23 and y <= max_y - 8 then 212 | confirm = true 213 | end 214 | end 215 | end 216 | end 217 | 218 | -- * ShowWarning 219 | -- Shows a SunShell warning with a customizable text 220 | function ShowWarning(text) 221 | confirm = false 222 | ErrorGenerator(text) 223 | max_y = error_lines[#error_lines][2] + 40 224 | Screen.fillEmptyRect(5,315,50,max_y,black,BOTTOM_SCREEN) 225 | Screen.fillRect(6,314,51,max_y-1,white,BOTTOM_SCREEN) 226 | Font.print(ttf,8,53,"Warning",selected,BOTTOM_SCREEN) 227 | for i,line in pairs(error_lines) do 228 | Font.print(ttf,8,line[2],line[1],black,BOTTOM_SCREEN) 229 | end 230 | Screen.fillEmptyRect(147,176,max_y - 23, max_y - 8,black,BOTTOM_SCREEN) 231 | Font.print(ttf,155,max_y - 23,"OK",black,BOTTOM_SCREEN) 232 | Screen.flip() 233 | Screen.refresh() 234 | Screen.fillEmptyRect(5,315,50,max_y,black,BOTTOM_SCREEN) 235 | Screen.fillRect(6,314,51,max_y-1,white,BOTTOM_SCREEN) 236 | Font.print(ttf,8,53,"Warning",selected,BOTTOM_SCREEN) 237 | for i,line in pairs(error_lines) do 238 | Font.print(ttf,8,line[2],line[1],black,BOTTOM_SCREEN) 239 | end 240 | Screen.fillEmptyRect(147,176,max_y - 23, max_y - 8,black,BOTTOM_SCREEN) 241 | Font.print(ttf,155,max_y - 23,"OK",black,BOTTOM_SCREEN) 242 | while not confirm do 243 | if (Controls.check(Controls.read(),KEY_TOUCH)) then 244 | x,y = Controls.readTouch() 245 | if x >= 147 and x <= 176 and y >= max_y - 23 and y <= max_y - 8 then 246 | confirm = true 247 | end 248 | end 249 | end 250 | end 251 | 252 | -- * LinesGenerator 253 | -- Similar to CropPrint but for TOP_SCREEN, you can see Applications src to know how to use it 254 | function LinesGenerator(text,y) 255 | error_lines = {} 256 | while string.len(text) > 60 do 257 | endl = 61 + LastSpace(string.sub(text,1,60)) 258 | table.insert(error_lines,{string.sub(text,1,endl), y}) 259 | text = string.sub(text,endl+1,-1) 260 | y = y + 15 261 | end 262 | if string.len(text) > 0 then 263 | table.insert(error_lines,{text, y}) 264 | end 265 | return error_lines 266 | end 267 | 268 | -- * DebugLinesGenerator 269 | -- Similar to CropPrint but for TOP_SCREEN, you can see Mail src to know how to use it 270 | function DebugLinesGenerator(text,y) 271 | error_lines = {} 272 | while string.len(text) > 40 do 273 | endl = 41 + LastSpace(string.sub(text,1,40)) 274 | table.insert(error_lines,{string.sub(text,1,endl), y}) 275 | text = string.sub(text,endl+1,-1) 276 | y = y + 15 277 | end 278 | if string.len(text) > 0 then 279 | table.insert(error_lines,{text, y}) 280 | end 281 | return error_lines 282 | end 283 | 284 | -- * AddIconTopbar 285 | -- Add an Icon to Topbar 286 | function AddIconTopbar(filename,id) 287 | table.insert(topbar_icons, {Graphics.loadImage(filename),id}) 288 | end 289 | 290 | -- * FreeIconTopbar 291 | -- Delete an Icon from Topbar 292 | function FreeIconTopbar(my_app) 293 | for i, icon in pairs(topbar_icons) do 294 | if icon[2] == my_app then 295 | Graphics.freeImage(icon[1]) 296 | table.remove(topbar_icons,i) 297 | break 298 | end 299 | end 300 | end 301 | 302 | -- * OneshotPrint 303 | -- Optimized generic print function for code which needs to be executed only one time 304 | function OneshotPrint(my_func) 305 | my_func() 306 | Screen.flip() 307 | Screen.refresh() 308 | my_func() 309 | end 310 | 311 | -- * CallKeyboard 312 | -- Calls a new instance for a Danzeff Keyboard 313 | function CallKeyboard(x, y) 314 | danzeff_mode = 1 315 | blockx = 2 316 | blocky = 2 317 | danzeff_x = x 318 | danzeff_y = y 319 | danzeff_map = { 320 | ",abc.def!ghi-jkl\009m n?opq(rst:uvw)xyz", 321 | "\0\0\0001\0\0\0002\0\0\0003\0\0\0004\009\0 5\0\0\0006\0\0\0007\0\0\0008\0\00009", 322 | "^ABC@DEF*GHI_JKL\009M N\"OPQ=RST;UVW/XYZ", 323 | "'(.)\"<'>-[_]!{?}\009\0 \0+\\=/:@;#~$`%*^|&" 324 | } 325 | pic1 = Graphics.loadImage(theme_dir.."/images/keys.png") 326 | pic2 = Graphics.loadImage(theme_dir.."/images/nums.png") 327 | pic3 = Graphics.loadImage(theme_dir.."/images/keys_c.png") 328 | pic4 = Graphics.loadImage(theme_dir.."/images/nums_c.png") 329 | pic1m = Graphics.loadImage(theme_dir.."/images/keys_t.png") 330 | pic2m = Graphics.loadImage(theme_dir.."/images/nums_t.png") 331 | pic3m = Graphics.loadImage(theme_dir.."/images/keys_c_t.png") 332 | pic4m = Graphics.loadImage(theme_dir.."/images/nums_c_t.png") 333 | olddanzpad = KEY_A 334 | end 335 | 336 | -- * ShowKeyboard 337 | -- Shows current keyboard instance 338 | function ShowKeyboard() 339 | if danzeff_mode == 1 then 340 | h = pic1 341 | Graphics.drawImage(danzeff_x, danzeff_y, pic1m) 342 | elseif danzeff_mode == 2 then 343 | h = pic2 344 | Graphics.drawImage(danzeff_x, danzeff_y, pic2m) 345 | elseif danzeff_mode == 3 then 346 | h = pic3 347 | Graphics.drawImage(danzeff_x, danzeff_y, pic3m) 348 | else 349 | h = pic4 350 | Graphics.drawImage(danzeff_x, danzeff_y, pic4m) 351 | end 352 | danx = (blockx - 1) * 50 353 | dany = (blocky - 1) * 50 354 | Graphics.drawPartialImage(danzeff_x + danx, danzeff_y + dany, danx, dany, 50, 50, h) 355 | end 356 | 357 | -- * KeyboardInput() 358 | -- Gets current keyboard input 359 | function KeyboardInput() 360 | cx, cy = Controls.readCirclePad() 361 | danzpad = Controls.read() 362 | posx = 2 363 | posy = 2 364 | if cx < -50 then 365 | posx = 1 366 | end 367 | if cx > 50 then 368 | posx = 3 369 | end 370 | if cy > 50 then 371 | posy = 1 372 | end 373 | if cy < -50 then 374 | posy = 3 375 | end 376 | if blocky ~= posy or blockx ~= posx then 377 | blocky = posy 378 | blockx = posx 379 | end 380 | if danzeff_mode > 2 then 381 | danzeff_mode = danzeff_mode - 2 382 | end 383 | if Controls.check(danzpad, KEY_R) and not Controls.check(olddanzpad, KEY_R) then 384 | if danzeff_mode == 1 then 385 | danzeff_mode = 2 386 | else 387 | danzeff_mode = 1 388 | end 389 | end 390 | if Controls.check(danzpad, KEY_R) then 391 | danzeff_mode = danzeff_mode + 2 392 | end 393 | charpos = (blocky - 1) * 12 + (blockx - 1) * 4 394 | if Controls.check(danzpad, KEY_Y) and not Controls.check(olddanzpad, KEY_Y) then 395 | res = string.byte(danzeff_map[danzeff_mode], charpos + 2) 396 | elseif Controls.check(danzpad, KEY_A) and not Controls.check(olddanzpad, KEY_A) then 397 | res = string.byte(danzeff_map[danzeff_mode], charpos + 4) 398 | elseif Controls.check(danzpad, KEY_B) and not Controls.check(olddanzpad, KEY_B) then 399 | res = string.byte(danzeff_map[danzeff_mode], charpos + 3) 400 | elseif Controls.check(danzpad, KEY_X) and not Controls.check(olddanzpad, KEY_X) then 401 | res = string.byte(danzeff_map[danzeff_mode], charpos + 1) 402 | else 403 | res = 0 404 | end 405 | olddanzpad = danzpad 406 | return res 407 | end 408 | 409 | -- * CloseKeyboard() 410 | -- Close current keyboard instance 411 | function CloseKeyboard() 412 | Graphics.freeImage(pic1) 413 | Graphics.freeImage(pic2) 414 | Graphics.freeImage(pic3) 415 | Graphics.freeImage(pic4) 416 | Graphics.freeImage(pic1m) 417 | Graphics.freeImage(pic2m) 418 | Graphics.freeImage(pic3m) 419 | Graphics.freeImage(pic4m) 420 | end -------------------------------------------------------------------------------- /LUA/modules/cia.lua: -------------------------------------------------------------------------------- 1 | -- Set private "Cia" mode 2 | mode = "Cia" 3 | 4 | -- Internal module settings 5 | master_index_cia = 0 6 | p_cia = 1 7 | if build == "Ninjhax 2" then 8 | ShowError("CIA manager unavailable on Ninjhax 2.") 9 | CallMainMenu() 10 | else 11 | cia_table = System.listCIA() 12 | update_list = true 13 | 14 | -- Game titles parsing 15 | dofile(main_dir.."/scripts/title_list.lua") 16 | assigned = 0 17 | for i,title in pairs(title_list) do 18 | if assigned >= (#cia_table - 1) then 19 | break 20 | end 21 | for z,app in pairs(cia_table) do 22 | if app.unique_id == title[3] then 23 | app["title_name"] = title[1] 24 | assigned = assigned + 1 25 | end 26 | end 27 | end 28 | end 29 | 30 | not_extracted = true 31 | function TableConcat(t1,t2) 32 | for i=1,#t2 do 33 | t1[#t1+1] = t2[i] 34 | end 35 | return t1 36 | end 37 | function listDirectory(dir) 38 | dir = System.listDirectory(dir) 39 | folders_table = {} 40 | files_table = {} 41 | for i,file in pairs(dir) do 42 | if file.directory then 43 | table.insert(folders_table,file) 44 | elseif (string.sub(file.name,-4) == ".cia") or (string.sub(file.name,-4) == ".CIA") then 45 | table.insert(files_table,file) 46 | end 47 | end 48 | table.sort(files_table, function (a, b) return (a.name:lower() < b.name:lower() ) end) 49 | table.sort(folders_table, function (a, b) return (a.name:lower() < b.name:lower() ) end) 50 | return_table = TableConcat(folders_table,files_table) 51 | return return_table 52 | end 53 | function UpdateSpace() 54 | free_space = System.getFreeSpace() 55 | f = "Bytes" 56 | f_free_space = free_space 57 | if free_space > 1024 then 58 | f_free_space = free_space / 1024 59 | f = "KBs" 60 | if f_free_space > 1024 then 61 | f_free_space = f_free_space / 1024 62 | f = "MBs" 63 | end 64 | end 65 | end 66 | UpdateSpace() 67 | files_table = listDirectory("/") 68 | int_mode = "CIA" 69 | function GetCategory(c) 70 | if c==0 then 71 | return "Application" 72 | elseif c==1 then 73 | return "System" 74 | elseif c==2 then 75 | return "Demo" 76 | elseif c==3 then 77 | return "Patch" 78 | elseif c==4 then 79 | return "TWL" 80 | end 81 | end 82 | function OpenDirectory(text,archive_id) 83 | i=0 84 | if text == ".." then 85 | j=-2 86 | while string.sub(System.currentDirectory(),j,j) ~= "/" do 87 | j=j-1 88 | end 89 | System.currentDirectory(string.sub(System.currentDirectory(),1,j)) 90 | else 91 | System.currentDirectory(System.currentDirectory()..text.."/") 92 | end 93 | files_table = listDirectory(System.currentDirectory()) 94 | if System.currentDirectory() ~= "/" then 95 | local extra = {} 96 | extra.name = ".." 97 | extra.size = 0 98 | extra.directory = true 99 | table.insert(files_table,extra) 100 | end 101 | end 102 | 103 | function ShowSDMCList() 104 | base_y = 0 105 | for l, file in pairs(files_table) do 106 | if (base_y > 226) then 107 | break 108 | end 109 | if (l >= master_index_cia) then 110 | if (l==p_cia) then 111 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 112 | color = selected 113 | else 114 | color = black 115 | end 116 | CropPrint(0,base_y,file.name,color,BOTTOM_SCREEN) 117 | base_y = base_y + 15 118 | end 119 | end 120 | end 121 | 122 | function ShowCIAList() 123 | base_y = 0 124 | for l, file in pairs(cia_table) do 125 | if (base_y > 226) then 126 | break 127 | end 128 | if (l >= master_index_cia) then 129 | if (l==p_cia) then 130 | base_y2 = base_y 131 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 132 | color = selected 133 | else 134 | color = black 135 | end 136 | if file.title_name == nil then 137 | CropPrint(0,base_y,file.product_id,color,BOTTOM_SCREEN) 138 | else 139 | CropPrint(0,base_y,file.title_name,color,BOTTOM_SCREEN) 140 | end 141 | base_y = base_y + 15 142 | end 143 | end 144 | end 145 | 146 | -- Rendering functions 147 | function AppTopScreenRender() 148 | Graphics.fillRect(5,395,40,220,black) 149 | Graphics.fillRect(6,394,41,219,white) 150 | end 151 | 152 | function AppBottomScreenRender() 153 | end 154 | 155 | -- Module main cycle 156 | function AppMainCycle() 157 | 158 | -- Reset internal variables 159 | sm_index = 1 160 | base_y = 0 161 | 162 | -- Draw bottom screen listmenu and top screen info 163 | Screen.debugPrint(9,175,"Free Space: "..f_free_space.." "..f,black,TOP_SCREEN) 164 | if int_mode == "SDMC" then 165 | 166 | -- Showing files list 167 | --if update_list then 168 | --BottomBGRefresh() 169 | ShowSDMCList() 170 | --update_list = false 171 | --end 172 | 173 | Screen.debugPrint(9,190,"SDMC listing",black,TOP_SCREEN) 174 | if not files_table[p_cia].directory then 175 | if not_extracted == true then 176 | cia_data = System.extractCIA(System.currentDirectory()..files_table[p_cia].name) 177 | end 178 | Screen.debugPrint(9,45,"Title: "..cia_data.title,black,TOP_SCREEN) 179 | Screen.debugPrint(9,60,"Unique ID: 0x"..string.sub(string.format('%02X',cia_data.unique_id),1,-3),black,TOP_SCREEN) 180 | tmp = io.open(System.currentDirectory()..files_table[p_cia].name,FREAD) 181 | size = io.size(tmp) 182 | f_size = size 183 | f2 = "Bytes" 184 | if size > 1024 then 185 | f_size = size / 1024 186 | f2 = "KBs" 187 | if f_size > 1024 then 188 | f_size = f_size / 1024 189 | f2 = "MBs" 190 | end 191 | end 192 | Screen.debugPrint(9,75,"Filesize: "..f_size.." "..f2,black,TOP_SCREEN) 193 | io.close(tmp) 194 | end 195 | else 196 | --if update_list then 197 | --BottomBGRefresh() 198 | ShowCIAList() 199 | --update_list = false 200 | --end 201 | Screen.debugPrint(9,190,"Imported CIA listing",black,TOP_SCREEN) 202 | Screen.debugPrint(9,45,"Unique ID: 0x"..string.sub(string.format('%02X',cia_table[p_cia].unique_id),1,-3),black,TOP_SCREEN) 203 | Screen.debugPrint(9,60,"Product ID: "..cia_table[p_cia].product_id,black,TOP_SCREEN) 204 | Screen.debugPrint(9,75,"Category: "..GetCategory(cia_table[p_cia].category),black,TOP_SCREEN) 205 | if (cia_table[p_cia].platform == 3) then 206 | Screen.debugPrint(9,90,"Platform: DSi",black,TOP_SCREEN) 207 | else 208 | Screen.debugPrint(9,90,"Platform: 3DS",black,TOP_SCREEN) 209 | end 210 | if cia_table[p_cia].mediatype == 1 then 211 | Screen.debugPrint(9,105,"Location: SDMC",Color.new(0,0,255),TOP_SCREEN) 212 | else 213 | Screen.debugPrint(9,105,"Location: NAND",Color.new(255,0,0),TOP_SCREEN) 214 | end 215 | end 216 | 217 | -- Sets controls triggering 218 | if Controls.check(pad,KEY_B) or Controls.check(pad,KEY_START) then 219 | CallMainMenu() 220 | elseif (Controls.check(pad,KEY_A) and not Controls.check(oldpad,KEY_A)) then 221 | oldpad = KEY_A 222 | if (int_mode == "SDMC") then 223 | sm_index = 1 224 | if (files_table[p_cia].directory) then 225 | OpenDirectory(files_table[p_cia].name) 226 | p_cia = 1 227 | master_index_cia = 0 228 | else 229 | if free_space - size > 0 then 230 | while true do 231 | Screen.waitVblankStart() 232 | Screen.refresh() 233 | pad = Controls.read() 234 | Screen.fillEmptyRect(60,260,50,82,black,BOTTOM_SCREEN) 235 | Screen.fillRect(61,259,51,81,white,BOTTOM_SCREEN) 236 | if (sm_index == 1) then 237 | Screen.fillRect(61,259,51,66,selected_item,BOTTOM_SCREEN) 238 | Screen.debugPrint(63,53,"Confirm",selected,BOTTOM_SCREEN) 239 | Screen.debugPrint(63,68,"Cancel",black,BOTTOM_SCREEN) 240 | if (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 241 | sm_index = 2 242 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 243 | TopCropPrint(9,100,"Importing " .. files_table[p_cia].name.."...",black,TOP_SCREEN) 244 | Screen.debugPrint(9,115,"Please wait...",black,TOP_SCREEN) 245 | Screen.flip() 246 | Screen.waitVblankStart() 247 | System.installCIA(System.currentDirectory()..files_table[p_cia].name) 248 | UpdateSpace() 249 | break 250 | end 251 | else 252 | Screen.fillRect(61,259,66,81,selected_item,BOTTOM_SCREEN) 253 | Screen.debugPrint(63,53,"Confirm",black,BOTTOM_SCREEN) 254 | Screen.debugPrint(63,68,"Cancel",selected,BOTTOM_SCREEN) 255 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 256 | sm_index = 1 257 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 258 | break 259 | end 260 | end 261 | oldpad = pad 262 | Screen.flip() 263 | end 264 | update_list = true 265 | end 266 | end 267 | else 268 | while true do 269 | Screen.waitVblankStart() 270 | Screen.refresh() 271 | pad = Controls.read() 272 | Screen.fillEmptyRect(60,260,50,82,black,BOTTOM_SCREEN) 273 | Screen.fillRect(61,259,51,81,white,BOTTOM_SCREEN) 274 | if (sm_index == 1) then 275 | Screen.fillRect(61,259,51,66,selected_item,BOTTOM_SCREEN) 276 | Screen.debugPrint(63,53,"Confirm",selected,BOTTOM_SCREEN) 277 | Screen.debugPrint(63,68,"Cancel",black,BOTTOM_SCREEN) 278 | if (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 279 | sm_index = 2 280 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 281 | System.uninstallCIA(cia_table[p_cia].access_id,cia_table[p_cia].mediatype) 282 | UpdateSpace() 283 | break 284 | end 285 | else 286 | Screen.fillRect(61,259,66,81,selected_item,BOTTOM_SCREEN) 287 | Screen.debugPrint(63,53,"Confirm",black,BOTTOM_SCREEN) 288 | Screen.debugPrint(63,68,"Cancel",selected,BOTTOM_SCREEN) 289 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 290 | sm_index = 1 291 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 292 | break 293 | end 294 | end 295 | Screen.flip() 296 | oldpad = pad 297 | end 298 | update_list = true 299 | cia_table = System.listCIA() 300 | if p_cia > #cia_table then 301 | p_cia = p_cia - 1 302 | end 303 | 304 | -- Game titles parsing 305 | assigned = 0 306 | for i,title in pairs(title_list) do 307 | if assigned >= (#cia_table - 1) then 308 | break 309 | end 310 | for z,app in pairs(cia_table) do 311 | if app.unique_id == title[3] then 312 | app["title_name"] = title[1] 313 | assigned = assigned + 1 314 | end 315 | end 316 | end 317 | 318 | end 319 | elseif (Controls.check(pad,KEY_Y) and not Controls.check(oldpad,KEY_Y)) then 320 | oldpad = KEY_A 321 | if (int_mode == "SDMC") then 322 | sm_index = 1 323 | if not (files_table[p_cia].directory) and (free_space - size > 0) then 324 | while true do 325 | Screen.waitVblankStart() 326 | Screen.refresh() 327 | pad = Controls.read() 328 | Screen.fillEmptyRect(60,260,50,82,black,BOTTOM_SCREEN) 329 | Screen.fillRect(61,259,51,81,white,BOTTOM_SCREEN) 330 | if (sm_index == 1) then 331 | Screen.fillRect(61,259,51,66,selected_item,BOTTOM_SCREEN) 332 | Screen.debugPrint(63,53,"Confirm",selected,BOTTOM_SCREEN) 333 | Screen.debugPrint(63,68,"Cancel",black,BOTTOM_SCREEN) 334 | if (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 335 | sm_index = 2 336 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 337 | TopCropPrint(9,100,"Importing " .. files_table[p_cia].name.."...",black,TOP_SCREEN) 338 | Screen.debugPrint(9,115,"Please wait...",black,TOP_SCREEN) 339 | Screen.flip() 340 | Screen.waitVblankStart() 341 | System.installCIA(System.currentDirectory()..files_table[p_cia].name,0) 342 | System.deleteFile(System.currentDirectory()..files_table[p_cia].name) 343 | UpdateSpace() 344 | files_table = listDirectory(System.currentDirectory()) 345 | if System.currentDirectory() ~= "/" then 346 | local extra = {} 347 | extra.name = ".." 348 | extra.size = 0 349 | extra.directory = true 350 | table.insert(files_table,extra) 351 | end 352 | if (p_cia > #files_table) then 353 | p_cia = p_cia - 1 354 | end 355 | break 356 | end 357 | else 358 | Screen.fillRect(61,259,66,81,selected_item,BOTTOM_SCREEN) 359 | Screen.debugPrint(63,53,"Confirm",selected,BOTTOM_SCREEN) 360 | Screen.debugPrint(63,68,"Cancel",black,BOTTOM_SCREEN) 361 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 362 | sm_index = 1 363 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 364 | break 365 | end 366 | end 367 | oldpad = pad 368 | Screen.flip() 369 | end 370 | update_list = true 371 | end 372 | else 373 | System.launchCIA(cia_table[p_cia].unique_id,cia_table[p_cia].mediatype) 374 | end 375 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 376 | not_extracted = true 377 | p_cia = p_cia - 1 378 | if (p_cia >= 16) then 379 | master_index_cia = p_cia - 15 380 | end 381 | update_list = true 382 | elseif (Controls.check(pad,KEY_DLEFT)) and not (Controls.check(oldpad,KEY_DLEFT)) then 383 | not_extracted = true 384 | p_cia = p_cia - 16 385 | if p_cia < 1 then 386 | p_cia = 1 387 | end 388 | if (p_cia >= 16) then 389 | master_index_cia = p_cia - 15 390 | else 391 | master_index_cia = 0 392 | end 393 | update_list = true 394 | elseif (Controls.check(pad,KEY_DRIGHT)) and not (Controls.check(oldpad,KEY_DRIGHT)) then 395 | not_extracted = true 396 | p_cia = p_cia + 16 397 | if ((p_cia > #files_table) and (int_mode == "SDMC")) then 398 | p_cia = #files_table 399 | elseif ((p_cia > #cia_table) and (int_mode == "CIA")) then 400 | p_cia = #cia_table 401 | end 402 | if (p_cia >= 17) then 403 | master_index_cia = p_cia - 15 404 | end 405 | update_list = true 406 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 407 | not_extracted = true 408 | p_cia = p_cia + 1 409 | if (p_cia >= 17) then 410 | master_index_cia = p_cia - 15 411 | end 412 | update_list = true 413 | end 414 | if (p_cia < 1) then 415 | if (int_mode == "SDMC") then 416 | p_cia = #files_table 417 | else 418 | p_cia = #cia_table 419 | end 420 | if (p_cia >= 17) then 421 | master_index_cia = p_cia - 15 422 | end 423 | elseif ((p_cia > #files_table) and (int_mode == "SDMC")) or ((p_cia > #cia_table) and (int_mode == "CIA")) then 424 | master_index_cia = 0 425 | p_cia = 1 426 | end 427 | if (Controls.check(pad,KEY_SELECT)) and not (Controls.check(oldpad,KEY_SELECT)) then 428 | update_list = true 429 | if int_mode=="CIA" then 430 | int_mode = "SDMC" 431 | else 432 | int_mode = "CIA" 433 | cia_table = System.listCIA() 434 | 435 | -- Game titles parsing 436 | assigned = 0 437 | for i,title in pairs(title_list) do 438 | if assigned >= (#cia_table - 1) then 439 | break 440 | end 441 | for z,app in pairs(cia_table) do 442 | if app.unique_id == title[3] then 443 | app["title_name"] = title[1] 444 | assigned = assigned + 1 445 | end 446 | end 447 | end 448 | 449 | end 450 | p_cia = 1 451 | master_index_cia = 0 452 | end 453 | end -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /LUA/modules/fb.lua: -------------------------------------------------------------------------------- 1 | -- Set private "FB" mode 2 | mode = "FB" 3 | 4 | -- Internal module settings 5 | update_bottom_screen = true 6 | update_top_screen = true 7 | DisableRenderer() 8 | ui_enabled = false 9 | red = Color.new(255,0,0) 10 | green = Color.new(0,255,0) 11 | menu_color = Color.new(255,255,255) 12 | selected_color = Color.new(0,255,0) 13 | selected_item = Color.new(0,0,200,50) 14 | copy_or_move = false 15 | function PrintControls() 16 | Screen.clear(TOP_SCREEN) 17 | Font.print(ttf,0,0,"Basic Controls:",white,TOP_SCREEN) 18 | Font.print(ttf,0,15,"A = Open file/folder",white,TOP_SCREEN) 19 | Font.print(ttf,0,30,"SELECT = Open file with...",white,TOP_SCREEN) 20 | Font.print(ttf,0,45,"R = Create new folder",white,TOP_SCREEN) 21 | Font.print(ttf,0,60,"X = File operations",white,TOP_SCREEN) 22 | Font.print(ttf,0,75,"Y = Transfer file between SD cards",white,TOP_SCREEN) 23 | Font.print(ttf,0,90,"B = Return Main Menu",white,TOP_SCREEN) 24 | Font.print(ttf,0,105,"---------------------------------",white,TOP_SCREEN) 25 | Font.print(ttf,0,120,"Opened file Controls:",white,TOP_SCREEN) 26 | Font.print(ttf,0,135,"X = Cancel file copy/move",white,TOP_SCREEN) 27 | Font.print(ttf,0,150,"Y = Confirm file copy/move",white,TOP_SCREEN) 28 | Font.print(ttf,0,165,"Left/Right = Pause/Resume",white,TOP_SCREEN) 29 | Font.print(ttf,0,180,"Left/Right = Extract icon (SMDH)",white,TOP_SCREEN) 30 | Font.print(ttf,0,195,"Left/Right = Scroll file",white,TOP_SCREEN) 31 | end 32 | function TableConcat(t1,t2) 33 | for i=1,#t2 do 34 | t1[#t1+1] = t2[i] 35 | end 36 | return t1 37 | end 38 | function SortDirectory(dir) 39 | folders_table = {} 40 | files_table = {} 41 | for i,file in pairs(dir) do 42 | if file.directory then 43 | table.insert(folders_table,file) 44 | else 45 | table.insert(files_table,file) 46 | end 47 | end 48 | table.sort(files_table, function (a, b) return (a.name:lower() < b.name:lower() ) end) 49 | table.sort(folders_table, function (a, b) return (a.name:lower() < b.name:lower() ) end) 50 | return_table = TableConcat(folders_table,files_table) 51 | return return_table 52 | end 53 | files_table = SortDirectory(System.listDirectory("/")) 54 | System.currentDirectory("/") 55 | p = 1 56 | current_file = nil 57 | current_type = nil 58 | big_image = false 59 | master_index = 0 60 | sm_index = 1 61 | sm_voices = {"Video Player","Music Player","Image Viewer","Text Reader","LUA Interpreter","HEX Viewer","3DSX Launcher","CIA Installer","SMDH Decoder","ZIP Extractor","Info Viewer","TTF Viewer","Cancel"} 62 | hex_values = {} 63 | hex_text = {} 64 | updateTXT = false 65 | select_mode = false 66 | update_main_extdata = true 67 | action_check = false 68 | move_base = nil 69 | copy_base = nil 70 | x_print = 0 71 | y_print = 0 72 | copy_type = 0 73 | move_type = 0 74 | txt_index = 0 75 | txt_words = 0 76 | txt_i = 0 77 | old_indexes = {} 78 | MAX_RAM_ALLOCATION = 10485760 79 | function DumpFile(input,archive) 80 | inp = io.open(extdata_directory..input,FREAD,archive) 81 | if System.doesFileExist("/"..input) then 82 | System.deleteFile("/"..input) 83 | end 84 | out = io.open("/"..string.format('%02X',archive).."_"..input,FCREATE) 85 | size = io.size(inp) 86 | index = 0 87 | while (index+(MAX_RAM_ALLOCATION/2) < size) do 88 | io.write(out,index,io.read(inp,index,MAX_RAM_ALLOCATION/2),(MAX_RAM_ALLOCATION/2)) 89 | index = index + (MAX_RAM_ALLOCATION/2) 90 | end 91 | if index < size then 92 | io.write(out,index,io.read(inp,index,size-index),(size-index)) 93 | end 94 | io.close(inp) 95 | io.close(out) 96 | end 97 | function CopyFile(input,output) 98 | inp = io.open(input,FREAD) 99 | if System.doesFileExist(output) then 100 | System.deleteFile(output) 101 | end 102 | out = io.open(output,FCREATE) 103 | size = io.size(inp) 104 | index = 0 105 | while (index+(MAX_RAM_ALLOCATION/2) < size) do 106 | io.write(out,index,io.read(inp,index,MAX_RAM_ALLOCATION/2),(MAX_RAM_ALLOCATION/2)) 107 | index = index + (MAX_RAM_ALLOCATION/2) 108 | end 109 | if index < size then 110 | io.write(out,index,io.read(inp,index,size-index),(size-index)) 111 | end 112 | io.close(inp) 113 | io.close(out) 114 | end 115 | function CopyDir(input,output) 116 | files = System.listDirectory(input) 117 | System.createDirectory(output) 118 | for z, file in pairs(files) do 119 | if (file.directory) then 120 | CopyDir(input.."/"..file.name,output.."/"..file.name) 121 | else 122 | CopyFile(input.."/"..file.name,output.."/"..file.name) 123 | end 124 | end 125 | end 126 | function ForceOpenFile(text, size, mode) 127 | update_top_screen = true 128 | if mode == "SMDH" then 129 | FBGC() 130 | current_type = "SMDH" 131 | current_file = System.extractSMDH(System.currentDirectory()..text) 132 | smdh_show = Console.new(TOP_SCREEN) 133 | Console.append(smdh_show,"Title: "..current_file.title.."\n\n") 134 | Console.append(smdh_show,"Description: "..current_file.desc.."\n\n") 135 | Console.append(smdh_show,"Author: "..current_file.author) 136 | elseif mode == "3DSX" and build == "3DSX" then 137 | FBGC() 138 | Screen.freeImage(bg) 139 | Sound.term() 140 | System.launch3DSX(System.currentDirectory()..text) 141 | elseif mode == "BMPV" then 142 | FBGC() 143 | current_file = io.open(System.currentDirectory()..text,FREAD) 144 | magic = io.read(current_file,0,4) 145 | io.close(current_file) 146 | if magic == "BMPV" then 147 | current_file = BMPV.load(System.currentDirectory()..text) 148 | current_type = "BMPV" 149 | BMPV.start(current_file,NO_LOOP,0x08,0x09) 150 | else 151 | current_file = JPGV.load(System.currentDirectory()..text) 152 | current_type = "JPGV" 153 | JPGV.start(current_file,NO_LOOP,0x08,0x09) 154 | end 155 | elseif mode == "WAV" then 156 | FBGC() 157 | current_file = io.open(System.currentDirectory()..text,FREAD) 158 | magic = io.read(current_file,8,4) 159 | io.close(current_file) 160 | if magic == "AIFF" then 161 | current_file = Sound.openAiff(System.currentDirectory()..text,true) 162 | current_type = "WAV" 163 | Sound.play(current_file,NO_LOOP,0x08,0x09) 164 | elseif magic == "OggS" then 165 | current_type = "" 166 | current_file = Sound.openOgg(System.currentDirectory()..text,true) 167 | Sound.play(current_file,NO_LOOP,0x08,0x09) 168 | else 169 | current_file = Sound.openWav(System.currentDirectory()..text,true) 170 | current_type = "WAV" 171 | Sound.play(current_file,NO_LOOP,0x08,0x09) 172 | end 173 | elseif mode == "IMG" then 174 | FBGC() 175 | current_type = "IMG" 176 | current_file = Graphics.loadImage(System.currentDirectory()..text) 177 | if Graphics.getImageWidth(current_file) > 400 then 178 | width = 400 179 | big_image = true 180 | end 181 | if Graphics.getImageHeight(current_file) > 240 then 182 | height = 240 183 | big_image = true 184 | end 185 | elseif mode == "LUA" then 186 | FBGC() 187 | GarbageCollection() 188 | for i,bg_apps_code in pairs(bg_apps) do 189 | bg_apps_code[2]() 190 | end 191 | for i,icon in pairs(topbar_icons) do 192 | Graphics.freeImage(icon[1]) 193 | end 194 | Sound.term() 195 | Font.unload(ttf) 196 | Graphics.term() 197 | reset_dir = System.currentDirectory() 198 | System.currentDirectory(string.sub(System.currentDirectory(),1,-2)) 199 | dofile(System.currentDirectory().."/"..text) 200 | System.currentDirectory(reset_dir) 201 | current_type = "LUA" 202 | Graphics.init() 203 | Sound.init() 204 | ttf = Font.load(theme_dir.."/fonts/main.ttf") 205 | elseif mode == "TXT" then 206 | FBGC() 207 | current_file = io.open(System.currentDirectory()..text,FREAD) 208 | text_console = Console.new(TOP_SCREEN) 209 | current_type = "TXT" 210 | txt_index = 0 211 | txt_words = 0 212 | updateTXT = true 213 | elseif mode == "HEX" then 214 | FBGC() 215 | current_file = io.open(System.currentDirectory()..text,FREAD) 216 | current_type = "HEX" 217 | txt_index = 0 218 | updateTXT = true 219 | elseif mode == "INFO" then 220 | FBGC() 221 | current_file = io.open(System.currentDirectory()..text,FREAD) 222 | current_type = "INFO" 223 | f_size = io.size(current_file) 224 | f = "Bytes" 225 | if (f_size > 1024) then 226 | f_size = f_size / 1024 227 | f = "KBs" 228 | end 229 | if (f_size > 1024) then 230 | f_size = f_size / 1024 231 | f = "MBs" 232 | end 233 | io.close(current_file) 234 | text_console = Console.new(TOP_SCREEN) 235 | Console.append(text_console,"Filename: "..text.."\n") 236 | i = -1 237 | while string.sub(text,i,i) ~= "." do 238 | i = i - 1 239 | end 240 | i = i + 1 241 | Console.append(text_console,"Format: "..string.upper(string.sub(text,i)).."\n") 242 | Console.append(text_console,"Size: "..f_size.." "..f.."\n") 243 | elseif mode == "ZIP" then 244 | FBGC() 245 | pass = System.startKeyboard("") 246 | System.extractZIP(System.currentDirectory()..text,System.currentDirectory()..string.sub(text,1,-5),pass) 247 | files_table = System.listDirectory(System.currentDirectory()) 248 | if System.currentDirectory() ~= "/" then 249 | local extra = {} 250 | extra.name = ".." 251 | extra.size = 0 252 | extra.directory = true 253 | table.insert(files_table,extra) 254 | end 255 | files_table = SortDirectory(files_table) 256 | elseif mode == "TTF" then 257 | FBGC() 258 | current_file = Font.load(System.currentDirectory()..text) 259 | current_type = "TTF" 260 | elseif mode == "CIA" then 261 | FBGC() 262 | sm_index = 1 263 | cia_data = System.extractCIA(System.currentDirectory()..text) 264 | oldpad = KEY_A 265 | while true do 266 | Screen.refresh() 267 | Screen.waitVblankStart() 268 | Screen.clear(TOP_SCREEN) 269 | Font.print(ttf,0,0,"Title: "..cia_data.title,white,TOP_SCREEN) 270 | Font.print(ttf,0,15,"Unique ID: 0x"..string.sub(string.format('%02X',cia_data.unique_id),1,-3),white,TOP_SCREEN) 271 | Controls.init() 272 | pad = Controls.read() 273 | Screen.fillEmptyRect(60,260,50,82,black,BOTTOM_SCREEN) 274 | Screen.fillRect(61,259,51,81,white,BOTTOM_SCREEN) 275 | if (sm_index == 1) then 276 | Screen.fillRect(61,259,51,66,green,BOTTOM_SCREEN) 277 | Font.print(ttf,63,53,"Confirm",red,BOTTOM_SCREEN) 278 | Font.print(ttf,63,68,"Cancel",black,BOTTOM_SCREEN) 279 | if (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 280 | sm_index = 2 281 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 282 | System.installCIA(System.currentDirectory()..text) 283 | break 284 | end 285 | else 286 | Screen.fillRect(61,259,66,81,green,BOTTOM_SCREEN) 287 | Font.print(ttf,63,53,"Confirm",black,BOTTOM_SCREEN) 288 | Font.print(ttf,63,68,"Cancel",red,BOTTOM_SCREEN) 289 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 290 | sm_index = 1 291 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 292 | break 293 | end 294 | end 295 | oldpad = pad 296 | Screen.flip() 297 | end 298 | end 299 | end 300 | function DeleteDir(dir) 301 | files = System.listDirectory(dir) 302 | for z, file in pairs(files) do 303 | if (file.directory) then 304 | DeleteDir(dir.."/"..file.name) 305 | else 306 | System.deleteFile(dir.."/"..file.name) 307 | end 308 | end 309 | System.deleteDirectory(dir) 310 | end 311 | function OpenFile(text, size) 312 | update_top_screen = true 313 | if string.upper(string.sub(text,-5)) == ".SMDH" then 314 | FBGC() 315 | current_type = "SMDH" 316 | current_file = System.extractSMDH(System.currentDirectory()..text) 317 | smdh_show = Console.new(TOP_SCREEN) 318 | Console.append(smdh_show,"Title: "..current_file.title.."\n\n") 319 | Console.append(smdh_show,"Description: "..current_file.desc.."\n\n") 320 | Console.append(smdh_show,"Author: "..current_file.author) 321 | elseif string.upper(string.sub(text,-4)) == ".ZIP" then 322 | FBGC() 323 | pass = System.startKeyboard("") 324 | System.extractZIP(System.currentDirectory()..text,System.currentDirectory()..string.sub(text,1,-5),pass) 325 | files_table = System.listDirectory(System.currentDirectory()) 326 | if System.currentDirectory() ~= "/" then 327 | local extra = {} 328 | extra.name = ".." 329 | extra.size = 0 330 | extra.directory = true 331 | table.insert(files_table,extra) 332 | end 333 | files_table = SortDirectory(files_table) 334 | elseif (string.upper(string.sub(text,-5)) == ".3DSX") and build == "Ninjhax 1" then 335 | FBGC() 336 | GarbageCollection() 337 | for i,bg_apps_code in pairs(bg_apps) do 338 | bg_apps_code[2]() 339 | end 340 | for i,icon in pairs(topbar_icons) do 341 | Graphics.freeImage(icon[1]) 342 | end 343 | Sound.term() 344 | Font.unload(ttf) 345 | Graphics.term() 346 | System.launch3DSX(System.currentDirectory()..text) 347 | elseif string.upper(string.sub(text,-5)) == ".BMPV" then 348 | FBGC() 349 | current_file = BMPV.load(System.currentDirectory()..text) 350 | current_type = "BMPV" 351 | BMPV.start(current_file,NO_LOOP,0x08,0x09) 352 | elseif string.upper(string.sub(text,-5)) == ".JPGV" then 353 | FBGC() 354 | current_file = JPGV.load(System.currentDirectory()..text) 355 | current_type = "JPGV" 356 | JPGV.start(current_file,NO_LOOP,0x08,0x09) 357 | elseif string.upper(string.sub(text,-4)) == ".TTF" then 358 | FBGC() 359 | current_file = Font.load(System.currentDirectory()..text) 360 | current_type = "TTF" 361 | elseif string.upper(string.sub(text,-4)) == ".WAV" then 362 | FBGC() 363 | current_file = Sound.openWav(System.currentDirectory()..text,true) 364 | current_type = "WAV" 365 | Sound.play(current_file,NO_LOOP,0x08,0x09) 366 | elseif string.upper(string.sub(text,-4)) == ".OGG" then 367 | FBGC() 368 | current_file = Sound.openOgg(System.currentDirectory()..text,true) 369 | current_type = "WAV" 370 | Sound.play(current_file,NO_LOOP,0x08,0x09) 371 | elseif string.upper(string.sub(text,-4)) == ".AIF" or string.upper(string.sub(text,-5)) == ".AIFF" then 372 | FBGC() 373 | current_file = Sound.openAiff(System.currentDirectory()..text,true) 374 | current_type = "WAV" 375 | Sound.play(current_file,NO_LOOP,0x08,0x09) 376 | elseif string.upper(string.sub(text,-4)) == ".PNG" or string.upper(string.sub(text,-4)) == ".BMP" or string.upper(string.sub(text,-4)) == ".JPG" then 377 | FBGC() 378 | current_type = "IMG" 379 | current_file = Graphics.loadImage(System.currentDirectory()..text) 380 | width = Graphics.getImageWidth(current_file) 381 | height = Graphics.getImageHeight(current_file) 382 | if width > 400 then 383 | width = 400 384 | big_image = true 385 | end 386 | if height > 240 then 387 | height = 240 388 | big_image = true 389 | end 390 | elseif string.upper(string.sub(text,-4)) == ".LUA" then 391 | FBGC() 392 | GarbageCollection() 393 | for i,bg_apps_code in pairs(bg_apps) do 394 | bg_apps_code[2]() 395 | end 396 | for i,icon in pairs(topbar_icons) do 397 | Graphics.freeImage(icon[1]) 398 | end 399 | Sound.term() 400 | Font.unload(ttf) 401 | Graphics.term() 402 | reset_dir = System.currentDirectory() 403 | System.currentDirectory(string.sub(System.currentDirectory(),1,-2)) 404 | dofile(System.currentDirectory().."/"..text) 405 | System.currentDirectory(reset_dir) 406 | current_type = "LUA" 407 | Sound.init() 408 | Graphics.init() 409 | ttf = Font.load(theme_dir.."/fonts/main.ttf") 410 | elseif string.upper(string.sub(text,-4)) == ".TXT" then 411 | FBGC() 412 | current_file = io.open(System.currentDirectory()..text,FREAD) 413 | text_console = Console.new(TOP_SCREEN) 414 | current_type = "TXT" 415 | txt_index = 0 416 | txt_words = 0 417 | updateTXT = true 418 | elseif string.upper(string.sub(text,-4)) == ".CIA" and build ~= "Ninjhax 2" then 419 | FBGC() 420 | sm_index = 1 421 | cia_data = System.extractCIA(System.currentDirectory()..text) 422 | oldpad = KEY_A 423 | while true do 424 | Screen.refresh() 425 | Screen.waitVblankStart() 426 | Screen.clear(TOP_SCREEN) 427 | Font.print(ttf,0,0,"Title: "..cia_data.title,white,TOP_SCREEN) 428 | Font.print(ttf,0,15,"Unique ID: 0x"..string.sub(string.format('%02X',cia_data.unique_id),1,-3),white,TOP_SCREEN) 429 | Controls.init() 430 | pad = Controls.read() 431 | Screen.fillEmptyRect(60,260,50,82,black,BOTTOM_SCREEN) 432 | Screen.fillRect(61,259,51,81,white,BOTTOM_SCREEN) 433 | if (sm_index == 1) then 434 | Screen.fillRect(61,259,51,66,green,BOTTOM_SCREEN) 435 | Font.print(ttf,63,53,"Confirm",red,BOTTOM_SCREEN) 436 | Font.print(ttf,63,68,"Cancel",black,BOTTOM_SCREEN) 437 | if (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 438 | sm_index = 2 439 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 440 | System.installCIA(System.currentDirectory()..text) 441 | break 442 | end 443 | else 444 | Screen.fillRect(61,259,66,81,green,BOTTOM_SCREEN) 445 | Font.print(ttf,63,53,"Confirm",black,BOTTOM_SCREEN) 446 | Font.print(ttf,63,68,"Cancel",red,BOTTOM_SCREEN) 447 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 448 | sm_index = 1 449 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 450 | break 451 | end 452 | end 453 | oldpad = pad 454 | Screen.flip() 455 | end 456 | end 457 | end 458 | function OpenDirectory(text,archive_id) 459 | update_bottom_screen = true 460 | i=0 461 | if text == ".." then 462 | j=-2 463 | while string.sub(System.currentDirectory(),j,j) ~= "/" do 464 | j=j-1 465 | end 466 | System.currentDirectory(string.sub(System.currentDirectory(),1,j)) 467 | else 468 | System.currentDirectory(System.currentDirectory()..text.."/") 469 | end 470 | files_table = System.listDirectory(System.currentDirectory()) 471 | if System.currentDirectory() ~= "/" then 472 | local extra = {} 473 | extra.name = ".." 474 | extra.size = 0 475 | extra.directory = true 476 | table.insert(files_table,extra) 477 | end 478 | files_table = SortDirectory(files_table) 479 | end 480 | function FBGC() 481 | if current_type == "SMDH" then 482 | Console.destroy(smdh_show) 483 | Screen.freeImage(current_file.icon) 484 | elseif current_type == "TTF" then 485 | Font.unload(current_file) 486 | elseif current_type == "BMPV" then 487 | if BMPV.isPlaying(current_file) then 488 | BMPV.stop(current_file) 489 | end 490 | BMPV.unload(current_file) 491 | elseif current_type == "JPGV" then 492 | if JPGV.isPlaying(current_file) then 493 | JPGV.stop(current_file) 494 | end 495 | JPGV.unload(current_file) 496 | elseif current_type == "WAV" then 497 | if Sound.isPlaying(current_file) then 498 | Sound.pause(current_file) 499 | end 500 | Sound.close(current_file) 501 | elseif current_type == "IMG" then 502 | Graphics.freeImage(current_file) 503 | big_image = false 504 | y_print = 0 505 | x_print = 0 506 | elseif current_type == "TXT" then 507 | io.close(current_file) 508 | Console.destroy(text_console) 509 | old_indexes = {} 510 | txt_i = 0 511 | elseif current_type == "INFO" then 512 | Console.destroy(text_console) 513 | elseif current_type == "HEX" then 514 | io.close(current_file) 515 | old_indexes = {} 516 | txt_i = 0 517 | end 518 | current_type = nil 519 | end 520 | function ThemePrint(x, y, text, color, screen) 521 | if string.len(text) > 40 then 522 | Font.print(ttf,x, y, string.sub(text,1,40) .. "...", color, screen) 523 | else 524 | Font.print(ttf,x, y, text, color, screen) 525 | end 526 | end 527 | function BuildLines(file, index) 528 | MAX_LENGTH = 1200 529 | SIZE = io.size(file) 530 | if ((index + MAX_LENGTH) < SIZE) then 531 | READ_LENGTH = MAX_LENGTH 532 | else 533 | READ_LENGTH = SIZE - index 534 | end 535 | if (index < SIZE) then 536 | Console.clear(text_console) 537 | Console.append(text_console,io.read(file,index,READ_LENGTH)) 538 | txt_words = Console.show(text_console) 539 | else 540 | txt_words = 0 541 | end 542 | if (txt_words > 0) then 543 | table.insert(old_indexes, index) 544 | index = index + txt_words 545 | txt_i = txt_i + 1 546 | end 547 | return index 548 | end 549 | function BuildHex(file, index) 550 | MAX_LENGTH = 120 551 | SIZE = io.size(file) 552 | if ((index + MAX_LENGTH) < SIZE) then 553 | READ_LENGTH = MAX_LENGTH 554 | else 555 | READ_LENGTH = SIZE - index 556 | end 557 | if (index < SIZE) then 558 | hex_text = {} 559 | hex_values = {} 560 | text = io.read(file,index,READ_LENGTH) 561 | t = 1 562 | while (t <= 15) do 563 | if ((t*8) > string.len(text)) then 564 | temp = string.sub(text,1+(t-1)*8,-1) 565 | else 566 | temp = string.sub(text,1+(t-1)*8,t*8) 567 | end 568 | t2 = 1 569 | while t2 <= string.len(temp) do 570 | table.insert(hex_values,string.byte(temp,t2)) 571 | t2 = t2 + 1 572 | end 573 | table.insert(hex_text,temp) 574 | t = t + 1 575 | end 576 | table.insert(old_indexes, index) 577 | index = index + READ_LENGTH 578 | txt_i = txt_i + 1 579 | end 580 | return index 581 | end 582 | function PrintSMDHInfo() 583 | Screen.clear(TOP_SCREEN) 584 | Console.show(smdh_show) 585 | Font.print(ttf,0,170,"Icon:",white,TOP_SCREEN) 586 | Screen.drawImage(0,185,current_file.icon,TOP_SCREEN) 587 | end 588 | function PrintText() 589 | Screen.clear(TOP_SCREEN) 590 | Console.show(text_console) 591 | end 592 | function PrintHex() 593 | Screen.clear(TOP_SCREEN) 594 | for l, line in pairs(hex_text) do 595 | Font.print(ttf,280,(l-1)*15,string.gsub(line,"\0"," "),white,TOP_SCREEN) 596 | temp = 1 597 | while (temp <= string.len(line)) do 598 | if (temp % 2 == 0) then 599 | Font.print(ttf,0+(temp-1)*30,(l-1)*15,string.format('%02X', hex_values[(l-1)*8+temp]),white,TOP_SCREEN) 600 | else 601 | Font.print(ttf,0+(temp-1)*30,(l-1)*15,string.format('%02X', hex_values[(l-1)*8+temp]),red,TOP_SCREEN) 602 | end 603 | temp = temp + 1 604 | end 605 | end 606 | Font.print(ttf,0,225,"Offset: 0x" .. string.format('%X', old_indexes[#old_indexes]) .. " (" .. (old_indexes[#old_indexes]) .. ")",white,TOP_SCREEN) 607 | end 608 | function TestFont() 609 | Screen.clear(TOP_SCREEN) 610 | Font.setPixelSizes(current_file,8) 611 | Font.print(current_file,0,5,"8: The quick brown fox",white,TOP_SCREEN) 612 | Font.print(current_file,10,13,"jumps over the lazy dog",white,TOP_SCREEN) 613 | Font.setPixelSizes(current_file,12) 614 | Font.print(current_file,0,25,"12: The quick brown fox",white,TOP_SCREEN) 615 | Font.print(current_file,10,37,"jumps over the lazy dog",white,TOP_SCREEN) 616 | Font.setPixelSizes(current_file,18) 617 | Font.print(current_file,0,54,"18: The quick brown fox",white,TOP_SCREEN) 618 | Font.print(current_file,10,72,"jumps over the lazy dog",white,TOP_SCREEN) 619 | Font.setPixelSizes(current_file,24) 620 | Font.print(current_file,0,95,"24: The quick brown fox",white,TOP_SCREEN) 621 | Font.print(current_file,10,119,"jumps over the lazy dog",white,TOP_SCREEN) 622 | Font.setPixelSizes(current_file,30) 623 | Font.print(current_file,0,149,"30: The quick brown fox",white,TOP_SCREEN) 624 | Font.print(current_file,10,179,"jumps over the lazy dog",white,TOP_SCREEN) 625 | end 626 | function ListMenu() 627 | base_y = 0 628 | Screen.clear(BOTTOM_SCREEN) 629 | for l, file in pairs(files_table) do 630 | if (base_y > 226) then 631 | break 632 | end 633 | if (l >= master_index) then 634 | if (l==p) then 635 | Screen.fillRect(0,319,base_y,base_y+15,selected_item,BOTTOM_SCREEN) 636 | color = selected_color 637 | else 638 | color = menu_color 639 | end 640 | CropPrint(0,base_y,file.name,color,BOTTOM_SCREEN) 641 | base_y = base_y + 15 642 | end 643 | end 644 | if move_base ~= nil then 645 | Font.print(ttf,300,0,"M",selected_color,BOTTOM_SCREEN) 646 | elseif copy_base ~= nil then 647 | Font.print(ttf,300,0,"C",selected_color,BOTTOM_SCREEN) 648 | end 649 | end 650 | 651 | -- Rendering functions 652 | function AppTopScreenRender() 653 | end 654 | 655 | function AppBottomScreenRender() 656 | end 657 | 658 | function BlitImage() 659 | if big_image then 660 | Graphics.drawPartialImage(0,0,x_print,y_print,width,height,current_file) 661 | x,y = Controls.readCirclePad() 662 | if (x < - 100) and (x_print > 0) then 663 | x_print = x_print - 5 664 | if x_print < 0 then 665 | x_print = 0 666 | end 667 | end 668 | if (y > 100) and (y_print > 0) then 669 | y_print = y_print - 5 670 | if y_print < 0 then 671 | y_print = 0 672 | end 673 | end 674 | if (x > 100) and (x_print + width < Graphics.getImageWidth(current_file)) then 675 | x_print = x_print + 5 676 | end 677 | if (y < - 100) and (y_print + height < Graphics.getImageHeight(current_file)) then 678 | y_print = y_print + 5 679 | end 680 | if x_print + width > Graphics.getImageWidth(current_file) then 681 | x_print = Graphics.getImageWidth(current_file) - width 682 | end 683 | if y_print + height > Graphics.getImageHeight(current_file) then 684 | y_print = Graphics.getImageHeight(current_file) - height 685 | end 686 | else 687 | Graphics.drawImage(0,0,current_file) 688 | end 689 | end 690 | 691 | -- Module main cycle 692 | function AppMainCycle() 693 | i = 1 694 | if update_top_screen then 695 | if (current_type == "SMDH") then 696 | OneshotPrint(PrintSMDHInfo) 697 | update_top_screen = false 698 | elseif (current_type == "TTF") then 699 | OneshotPrint(TestFont) 700 | update_top_screen = false 701 | elseif (current_type == "BMPV") then 702 | BMPV.draw(0,0,current_file,TOP_SCREEN) 703 | elseif (current_type == "JPGV") then 704 | JPGV.draw(0,0,current_file,TOP_SCREEN) 705 | elseif (current_type == "WAV") then 706 | Screen.clear(TOP_SCREEN) 707 | Sound.updateStream() 708 | Font.print(ttf,0,0,"Title: ",white,TOP_SCREEN) 709 | ThemePrint(0,15,Sound.getTitle(current_file),white,TOP_SCREEN) 710 | Font.print(ttf,0,40,"Author: ",white,TOP_SCREEN) 711 | ThemePrint(0,55,Sound.getAuthor(current_file),white,TOP_SCREEN) 712 | Font.print(ttf,0,80,"Time: "..FormatTime(Sound.getTime(current_file)).." / "..FormatTime(Sound.getTotalTime(current_file)),white,TOP_SCREEN) 713 | Font.print(ttf,0,95,"Samplerate: "..Sound.getSrate(current_file),white,TOP_SCREEN) 714 | if Sound.getType(current_file) == 1 then 715 | stype = "Mono" 716 | else 717 | stype = "Stereo" 718 | end 719 | Font.print(ttf,0,110,"Audiotype: "..stype,white,TOP_SCREEN) 720 | elseif (current_type == "INFO") then 721 | OneshotPrint(PrintText) 722 | update_top_screen = false 723 | elseif (current_type == "TXT") then 724 | if (updateTXT) then 725 | txt_index = BuildLines(current_file,txt_index) 726 | updateTXT = false 727 | end 728 | OneshotPrint(PrintText) 729 | update_top_screen = false 730 | elseif (current_type == "HEX") then 731 | if (updateTXT) then 732 | txt_index = BuildHex(current_file,txt_index) 733 | updateTXT = false 734 | end 735 | OneshotPrint(PrintHex) 736 | update_top_screen = false 737 | else 738 | OneshotPrint(PrintControls) 739 | update_top_screen = false 740 | end 741 | end 742 | if current_type == "IMG" then 743 | CustomRenderTop(BlitImage) 744 | end 745 | if update_bottom_screen then 746 | OneshotPrint(ListMenu) 747 | update_bottom_screen = false 748 | end 749 | 750 | -- Select Mode Controls Functions 751 | if (select_mode) then 752 | Screen.fillEmptyRect(60,260,35,232,black,BOTTOM_SCREEN) 753 | Screen.fillRect(61,259,36,231,white,BOTTOM_SCREEN) 754 | for l, voice in pairs(sm_voices) do 755 | if (l == sm_index) then 756 | Screen.fillRect(61,259,36+(l-1)*15,36+l*15,green,BOTTOM_SCREEN) 757 | Font.print(ttf,63,36+(l-1)*15,voice,red,BOTTOM_SCREEN) 758 | else 759 | Font.print(ttf,63,36+(l-1)*15,voice,black,BOTTOM_SCREEN) 760 | end 761 | end 762 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 763 | sm_index = sm_index - 1 764 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 765 | sm_index = sm_index + 1 766 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 767 | if (sm_index == 1) then 768 | ForceOpenFile(files_table[p].name,files_table[p].size,"BMPV") 769 | elseif (sm_index == 2) then 770 | ForceOpenFile(files_table[p].name,files_table[p].size,"WAV") 771 | elseif (sm_index == 3) then 772 | ForceOpenFile(files_table[p].name,files_table[p].size,"IMG") 773 | elseif (sm_index == 4) then 774 | ForceOpenFile(files_table[p].name,files_table[p].size,"TXT") 775 | elseif (sm_index == 5) then 776 | ForceOpenFile(files_table[p].name,files_table[p].size,"LUA") 777 | elseif (sm_index == 6) then 778 | ForceOpenFile(files_table[p].name,files_table[p].size,"HEX") 779 | elseif (sm_index == 7) then 780 | if build == "Custom Firmware" or build == "Ninjhax 2" then 781 | ShowError("3DSX Launcher not available on Custom Firmwares and Ninjhax 2.") 782 | else 783 | ForceOpenFile(files_table[p].name,files_table[p].size,"3DSX") 784 | end 785 | elseif (sm_index == 8) then 786 | if build == "Ninjhax 2" then 787 | ShowError("CIA Installer not available on Ninjhax 2.") 788 | else 789 | ForceOpenFile(files_table[p].name,files_table[p].size,"CIA") 790 | end 791 | elseif (sm_index == 9) then 792 | ForceOpenFile(files_table[p].name,files_table[p].size,"SMDH") 793 | elseif (sm_index == 10) then 794 | ForceOpenFile(files_table[p].name,files_table[p].size,"ZIP") 795 | elseif (sm_index == 11) then 796 | ForceOpenFile(files_table[p].name,files_table[p].size,"INFO") 797 | elseif (sm_index == 12) then 798 | ForceOpenFile(files_table[p].name,files_table[p].size,"TTF") 799 | end 800 | sm_index = 1 801 | select_mode = false 802 | update_bottom_screen = true 803 | elseif (Controls.check(pad,KEY_START)) then 804 | FBGC() 805 | GarbageCollection() 806 | for i,bg_apps_code in pairs(bg_apps) do 807 | bg_apps_code[2]() 808 | end 809 | for i,icon in pairs(topbar_icons) do 810 | Graphics.freeImage(icon[1]) 811 | end 812 | Sound.term() 813 | Font.unload(ttf) 814 | Graphics.term() 815 | if start_dir == "/" and build ~= "Custom Firmware" then -- boot.3dsx patch 816 | System.reboot() 817 | else 818 | System.exit() 819 | end 820 | end 821 | if (sm_index < 1) then 822 | sm_index = #sm_voices 823 | elseif (sm_index > #sm_voices) then 824 | sm_index = 1 825 | end 826 | -- Action Check 827 | elseif (action_check) then 828 | Screen.fillEmptyRect(60,260,50,127,black,BOTTOM_SCREEN) 829 | Screen.fillRect(61,259,51,126,white,BOTTOM_SCREEN) 830 | if sm_index > 5 then 831 | sm_index = 1 832 | elseif sm_index < 1 then 833 | sm_index = 5 834 | end 835 | colors = {black,black,black,black,black} 836 | colors[sm_index] = red 837 | Screen.fillRect(61,259,36+sm_index*15,51+sm_index*15,green,BOTTOM_SCREEN) 838 | Font.print(ttf,63,51,"Delete",colors[1],BOTTOM_SCREEN) 839 | Font.print(ttf,63,66,"Rename",colors[2],BOTTOM_SCREEN) 840 | Font.print(ttf,63,81,"Copy",colors[3],BOTTOM_SCREEN) 841 | Font.print(ttf,63,96,"Move",colors[4],BOTTOM_SCREEN) 842 | Font.print(ttf,63,111,"Cancel",colors[5],BOTTOM_SCREEN) 843 | if (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 844 | sm_index = sm_index + 1 845 | elseif (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 846 | sm_index = sm_index - 1 847 | elseif (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 848 | if sm_index == 1 then 849 | update_bottom_screen = true 850 | if (files_table[p].directory) then 851 | if (files_table[p].name ~= "..") then 852 | DeleteDir(System.currentDirectory()..files_table[p].name) 853 | end 854 | else 855 | System.deleteFile(System.currentDirectory()..files_table[p].name) 856 | end 857 | while (#files_table > 0) do 858 | table.remove(files_table) 859 | end 860 | files_table = System.listDirectory(System.currentDirectory()) 861 | if System.currentDirectory() ~= "/" then 862 | local extra = {} 863 | extra.name = ".." 864 | extra.size = 0 865 | extra.directory = true 866 | table.insert(files_table,extra) 867 | end 868 | files_table = SortDirectory(files_table) 869 | if (p > #files_table) then 870 | p = p - 1 871 | end 872 | action_check = false 873 | elseif sm_index == 2 then 874 | update_bottom_screen = true 875 | if (files_table[p].name ~= "..") then 876 | new_name = System.startKeyboard(files_table[p].name) 877 | pad = KEY_A 878 | oldpad = KEY_A 879 | if (files_table[p].directory) then 880 | System.renameDirectory(System.currentDirectory() .. files_table[p].name,System.currentDirectory() .. new_name) 881 | else 882 | System.renameFile(System.currentDirectory() .. files_table[p].name,System.currentDirectory() .. new_name) 883 | end 884 | files_table = System.listDirectory(System.currentDirectory()) 885 | if System.currentDirectory() ~= "/" then 886 | local extra = {} 887 | extra.name = ".." 888 | extra.size = 0 889 | extra.directory = true 890 | table.insert(files_table,extra) 891 | end 892 | files_table = SortDirectory(files_table) 893 | end 894 | action_check = false 895 | sm_index = 1 896 | elseif sm_index == 3 then 897 | if (files_table[p].directory) then 898 | if (files_table[p].name ~= "..") then 899 | copy_name = files_table[p].name 900 | copy_base = System.currentDirectory() .. files_table[p].name 901 | copy_type = 0 902 | end 903 | else 904 | copy_type = 1 905 | copy_name = files_table[p].name 906 | copy_base = System.currentDirectory() .. files_table[p].name 907 | end 908 | action_check = false 909 | sm_index = 1 910 | elseif sm_index == 4 then 911 | if (files_table[p].name ~= "..") then 912 | update_bottom_screen = true 913 | move_base = System.currentDirectory() .. files_table[p].name 914 | move_name = files_table[p].name 915 | if (files_table[p].directory) then 916 | move_type = 0 917 | else 918 | move_type = 1 919 | end 920 | end 921 | action_check = false 922 | sm_index = 1 923 | elseif sm_index == 5 then 924 | update_bottom_screen = true 925 | sm_index = 1 926 | action_check = false 927 | end 928 | end 929 | else 930 | -- Base Controls Functions 931 | if (Controls.check(pad,KEY_DUP)) and not (Controls.check(oldpad,KEY_DUP)) then 932 | update_bottom_screen = true 933 | p = p - 1 934 | if (p >= 16) then 935 | master_index = p - 15 936 | end 937 | elseif (Controls.check(pad,KEY_DDOWN)) and not (Controls.check(oldpad,KEY_DDOWN)) then 938 | update_bottom_screen = true 939 | p = p + 1 940 | if (p >= 17) then 941 | master_index = p - 15 942 | end 943 | end 944 | if (p < 1) then 945 | p = #files_table 946 | if (p >= 17) then 947 | master_index = p - 15 948 | end 949 | elseif (p > #files_table) then 950 | master_index = 0 951 | p = 1 952 | end 953 | if (Controls.check(pad,KEY_A)) and not (Controls.check(oldpad,KEY_A)) then 954 | if (files_table[p].directory) then 955 | OpenDirectory(files_table[p].name,0) 956 | p=1 957 | master_index=0 958 | else 959 | OpenFile(files_table[p].name,files_table[p].size) 960 | end 961 | elseif (Controls.check(pad,KEY_X)) and not (Controls.check(oldpad,KEY_X)) then 962 | if (move_base == nil) and (copy_base == nil) then 963 | action_check = true 964 | else 965 | update_bottom_screen = true 966 | move_base = nil 967 | move_name = nil 968 | copy_base = nil 969 | copy_name = nil 970 | end 971 | elseif (Controls.check(pad,KEY_Y)) and not (Controls.check(oldpad,KEY_Y)) then 972 | if (move_base ~= nil) then 973 | update_bottom_screen = true 974 | if (move_type == 0) then 975 | System.renameDirectory(move_base,System.currentDirectory() .. move_name) 976 | else 977 | System.renameFile(move_base,System.currentDirectory() .. move_name) 978 | end 979 | move_base = nil 980 | files_table = System.listDirectory(System.currentDirectory()) 981 | if System.currentDirectory() ~= "/" then 982 | local extra = {} 983 | extra.name = ".." 984 | extra.size = 0 985 | extra.directory = true 986 | table.insert(files_table,extra) 987 | end 988 | files_table = SortDirectory(files_table) 989 | elseif (copy_base ~= nil) then 990 | copy_end = System.currentDirectory() .. copy_name 991 | if copy_end == copy_base then 992 | temp_copy = "Copy_" .. copy_name 993 | copy_end = System.currentDirectory() .. temp_copy 994 | if (copy_type == 1) then 995 | while System.doesFileExist(copy_end) do 996 | temp_copy = "Copy_" .. temp_copy 997 | copy_end = System.currentDirectory() .. temp_copy 998 | end 999 | end 1000 | end 1001 | if (copy_type == 0) then 1002 | CopyDir(copy_base,copy_end) 1003 | else 1004 | CopyFile(copy_base,copy_end) 1005 | end 1006 | files_table = System.listDirectory(System.currentDirectory()) 1007 | if System.currentDirectory() ~= "/" then 1008 | local extra = {} 1009 | extra.name = ".." 1010 | extra.size = 0 1011 | extra.directory = true 1012 | table.insert(files_table,extra) 1013 | end 1014 | files_table = SortDirectory(files_table) 1015 | copy_name = nil 1016 | copy_base = nil 1017 | else 1018 | update_top_screen = true 1019 | FBGC() 1020 | if (files_table[p].directory == false) then 1021 | inp = io.open(System.currentDirectory()..files_table[p].name,FREAD) 1022 | filesize = io.size(inp) 1023 | if filesize > MAX_RAM_ALLOCATION * 2 then 1024 | ShowError("This file is too large for this transfer mode.") 1025 | else 1026 | file_ptr = io.read(inp,0,filesize) 1027 | io.close(inp) 1028 | ShowWarning("Insert the SD card where the file needs to be sent and then press OK.") 1029 | --while not System.checkSDMC() do TODO: Add this feature in public lpp-3ds 1030 | -- ShowError("Please insert an SD card in your console to transfer a file.") 1031 | --end 1032 | out = io.open("/"..files_table[p].name,FCREATE) 1033 | io.write(out,0,file_ptr,filesize) 1034 | io.close(out) 1035 | System.currentDirectory("/") 1036 | update_bottom_screen = true 1037 | files_table = System.listDirectory(System.currentDirectory()) 1038 | if System.currentDirectory() ~= "/" then 1039 | local extra = {} 1040 | extra.name = ".." 1041 | extra.size = 0 1042 | extra.directory = true 1043 | table.insert(files_table,extra) 1044 | end 1045 | file_ptr = nil 1046 | files_table = SortDirectory(files_table) 1047 | end 1048 | oldpad = KEY_A 1049 | end 1050 | end 1051 | elseif (Controls.check(pad,KEY_B)) and not (Controls.check(oldpad,KEY_B)) then 1052 | FBGC() 1053 | CallMainMenu() 1054 | elseif (Controls.check(pad,KEY_DLEFT)) and not (Controls.check(oldpad,KEY_DLEFT)) then 1055 | if (current_type == "SMDH") then 1056 | update_bottom_screen = true 1057 | name = System.startKeyboard("icon.bmp") 1058 | Screen.saveBitmap(current_file.icon,System.currentDirectory()..name) 1059 | oldpad = KEY_A 1060 | files_table = System.listDirectory(System.currentDirectory()) 1061 | if System.currentDirectory() ~= "/" then 1062 | local extra = {} 1063 | extra.name = ".." 1064 | extra.size = 0 1065 | extra.directory = true 1066 | table.insert(files_table,extra) 1067 | end 1068 | files_table = SortDirectory(files_table) 1069 | elseif (current_type == "TXT") or (current_type == "HEX") then 1070 | if (txt_i > 1) then 1071 | update_top_screen = true 1072 | updateTXT = true 1073 | table.remove(old_indexes) 1074 | txt_index = table.remove(old_indexes) 1075 | txt_i = txt_i - 2 1076 | end 1077 | elseif (current_type == "WAV") then 1078 | if (Sound.isPlaying(current_file)) then 1079 | Sound.pause(current_file) 1080 | else 1081 | Sound.resume(current_file) 1082 | end 1083 | elseif (current_type == "BMPV") then 1084 | if (BMPV.isPlaying(current_file)) then 1085 | BMPV.pause(current_file) 1086 | else 1087 | BMPV.resume(current_file) 1088 | end 1089 | elseif (current_type == "JPGV") then 1090 | if (JPGV.isPlaying(current_file)) then 1091 | JPGV.pause(current_file) 1092 | else 1093 | JPGV.resume(current_file) 1094 | end 1095 | end 1096 | elseif (Controls.check(pad,KEY_DRIGHT)) and not (Controls.check(oldpad,KEY_DRIGHT)) then 1097 | if (current_type == "SMDH") then 1098 | update_bottom_screen = true 1099 | name = System.startKeyboard("icon.bmp") 1100 | Screen.saveBitmap(current_file.icon,System.currentDirectory()..name) 1101 | oldpad = KEY_A 1102 | files_table = System.listDirectory(System.currentDirectory()) 1103 | if System.currentDirectory() ~= "/" then 1104 | local extra = {} 1105 | extra.name = ".." 1106 | extra.size = 0 1107 | extra.directory = true 1108 | table.insert(files_table,extra) 1109 | end 1110 | files_table = SortDirectory(files_table) 1111 | elseif (current_type == "TXT") or (current_type == "HEX") then 1112 | update_top_screen = true 1113 | updateTXT = true 1114 | elseif (current_type == "WAV") then 1115 | if (Sound.isPlaying(current_file)) then 1116 | Sound.pause(current_file) 1117 | else 1118 | Sound.resume(current_file) 1119 | end 1120 | elseif (current_type == "BMPV") then 1121 | if (BMPV.isPlaying(current_file)) then 1122 | BMPV.pause(current_file) 1123 | else 1124 | BMPV.resume(current_file) 1125 | end 1126 | elseif (current_type == "JPGV") then 1127 | if (JPGV.isPlaying(current_file)) then 1128 | JPGV.pause(current_file) 1129 | else 1130 | JPGV.resume(current_file) 1131 | end 1132 | end 1133 | elseif (Controls.check(pad,KEY_SELECT)) and not (Controls.check(oldpad,KEY_SELECT)) then 1134 | if (files_table[p].directory == false) then 1135 | select_mode = true 1136 | end 1137 | elseif (Controls.check(pad,KEY_R)) and not (Controls.check(oldpad,KEY_R)) then 1138 | update_bottom_screen = true 1139 | name = System.startKeyboard("New Folder") 1140 | System.createDirectory(System.currentDirectory()..name) 1141 | pad = KEY_A 1142 | files_table = System.listDirectory(System.currentDirectory()) 1143 | if System.currentDirectory() ~= "/" then 1144 | local extra = {} 1145 | extra.name = ".." 1146 | extra.size = 0 1147 | extra.directory = true 1148 | table.insert(files_table,extra) 1149 | end 1150 | files_table = SortDirectory(files_table) 1151 | elseif (Controls.check(pad,KEY_TOUCH)) then 1152 | update_bottom_screen = true 1153 | x,y = Controls.readTouch() 1154 | new_index = math.ceil(y/15) 1155 | if (new_index <= #files_table) then 1156 | if master_index > 0 then 1157 | p = new_index + master_index - 1 1158 | else 1159 | p = new_index 1160 | end 1161 | end 1162 | end 1163 | end 1164 | if (Controls.check(oldpad,KEY_TOUCH)) and not (Controls.check(pad,KEY_TOUCH)) then 1165 | update_bottom_screen = true 1166 | master_index = p - 15 1167 | end 1168 | end --------------------------------------------------------------------------------