├── CMakeLists.txt ├── LICENSE ├── README.md ├── VolLightbarCtrlWithDS4.yml └── main.c /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) 4 | if(DEFINED ENV{DOLCESDK}) 5 | set(CMAKE_TOOLCHAIN_FILE "$ENV{DOLCESDK}/share/dolce.toolchain.cmake" CACHE PATH "toolchain file") 6 | else() 7 | message(FATAL_ERROR "Please define DOLCESDK to point to your SDK path!") 8 | endif() 9 | endif() 10 | 11 | project(VolLightbarCtrlWithDS4) 12 | include("${DOLCESDK}/share/dolce.cmake" REQUIRED) 13 | 14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-q -Wall -O3 -std=gnu99") 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions") 16 | 17 | link_directories( 18 | ${CMAKE_CURRENT_BINARY_DIR} 19 | ) 20 | 21 | if (NOT ${RELEASE}) 22 | add_definitions(-DENABLE_LOGGING) 23 | endif() 24 | 25 | add_executable(${PROJECT_NAME} 26 | main.c 27 | ) 28 | 29 | target_link_libraries(${PROJECT_NAME} 30 | taihenForKernel_stub 31 | taihenModuleUtils_stub 32 | SceLibKernel_stub 33 | SceCtrlForDriver_stub 34 | SceModulemgrForKernel_stub 35 | SceDebugForDriver_stub 36 | SceSysmemForDriver_stub 37 | SceThreadmgrForDriver_stub 38 | SceSblAIMgrForDriver_stub 39 | SceRegMgrForDriver_stub 40 | SceDisplayForDriver_stub 41 | SceSysclibForDriver_stub 42 | SceSblACMgrForDriver_stub 43 | SceAppMgrForDriver_stub 44 | SceSysrootForKernel_stub 45 | SceIofilemgrForDriver_stub 46 | ) 47 | 48 | set_target_properties(${PROJECT_NAME} 49 | PROPERTIES LINK_FLAGS "-nostdlib" 50 | ) 51 | 52 | dolce_create_self(${PROJECT_NAME}.skprx ${PROJECT_NAME} 53 | UNSAFE 54 | CONFIG ${CMAKE_SOURCE_DIR}/${PROJECT_NAME}.yml 55 | ) 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Brandon Keyser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Preface 2 | This was the first project I've ever written in c. Most of my programming experience comes from VBA so I had a lot of help on this project and learned a lot (see credit section at bottom). 3 | 4 | # VolLightbarCtrlWithDS4 5 | Kernel plugin for PS Vita. Requires vita firmware 3.60, Henkaku Enso, and the Minivitatv plugin to work. 6 | Also supports PSTV. 7 | 8 | # Installation Instructions 9 | 1) Download zip from latest release 10 | 2) Copy VolLightbarCtrlWithDS4.skprx to ur0:/tai folder on ps vita 11 | 3) Put "ur0:/tai/VolLightbarCtrlWithDS4.skprx" (without quotes) under KERNEL section in Config.txt file 12 | 4) Reboot vita 13 | 14 | # What it does 15 | Control volume using DS4 controller. 16 | DS4 lightbar used as DS4 battery indicator. 17 | Adjust DS4 lightbar brightness. 18 | Supports up to 4 connected controllers. 19 | 20 | # How to use 21 | Press Select and L1 simultaneously to lower volume 22 | Press Select and R1 simultaneously to raise volume 23 | Press Select, L1, and R1 simultaneously to mute volume 24 | Press L1, L2, R1, and R2 simultaneously to toggle DS4 lightbar brightess 25 | 26 | Magenta lightbar means DS4 is charging 27 | Turquoise lightbar means DS4 is fully charged 28 | Green lightbar means DS4 is high charge 29 | Yellow lightbar means DS4 is medium charge 30 | Red lightbar means DS4 is low charge 31 | Red pulsing lightbar means DS4 will run out of charge soon 32 | 33 | # Credits 34 | xerpi 35 | -for directing me to the discord henkaku server 36 | 37 | marburg 38 | -for providing lightbar mod example code 39 | -letting me know to use SceLibKernel_stub in my cmakelists.txt 40 | -for letting me know that a function in my user thread could be interrupting the reboot sequence 41 | 42 | SKGleba 43 | -for confirming that the basic difference between user and kernel is privilege. 44 | -for teaching me that *ALL in config.txt includes *main 45 | -for teaching me that the format for checking if titleid string belongs to main --> titleid[0]=='\0' 46 | -for informing me of ksceDebugPrintf (kernel printf function) 47 | -for giving me the idea to use db.yml to find required stubs 48 | -for informing me that I need to have taimoduleutils_stub for module_get_offset to work in kernel plugin 49 | -for suggesting to reverse engineer sceAVConfigGetSystemVol, sceAVConfigSetSystemVol, sceAVConfigMuteOn functions 50 | -for suggesting tools/programs to use for reverse engineering functions 51 | -for informing me that bootimage is just a bunch of elfs appended to each other and then encrypted to one self 52 | 53 | davee 54 | -for teaching me that you should choose user over kernel everytime you get the option because with less privileges, you're less likely to cause system crashes if something goes wrong. 55 | -for teaching me what "nostdlib" link flag does in cmakelists.txt 56 | -for teaching me that plugins are injected into whatever process is described in the config.txt. It's lifetime is determined by whatever process it lives in. 57 | -for teaching me that *ALL in config.txt includes *main 58 | -for teaching me that adding a delay in an infinite loop provides cooperative scheduling to prevent my plugin from hogging the cpu 59 | -for informing me that using sceKernelDelayThread in a hook is bad form 60 | 61 | Bythos 62 | -for letting me know minivitatv plugin supports lightbar and DS4vita doesn't 63 | -for supplying me with the user function used to print to PrincessLog and how to use it 64 | -for helping me setup PrincessLog and getting it to work 65 | -for the suggestion of using |= instead of = to assign volume buttons to pad_data->buttons 66 | -for suggesting that I need to hook sceCtrlPeekBufferPositive2 for LiveArea to recognize my inputs because SceShell imports sceCtrlPeekBufferPositive2 67 | -for suggesting one can use userspace functions in kernel if they get the address of the module in memory and use offsets. Then hook the function offsets (taihen). 68 | -for suggesting how to simplify my if statements in the sceCtrlPeekBufferPositive2 hook function 69 | -for informing me that all functions in same module have same base address 70 | -for informing me that known kernel functions in db.yml can be hooked by export and don't require offset. All libraries in db.yml that have kernel set to true, have the named functions exported from the kernel. 71 | -for informing me that hooking imports requires NID hooking 72 | -for informing me that the kernel exports functions so that other apps can import them. So when you hook a function export, the code gets run every single time an application uses that same function as an import. 73 | -for informing me that Module = ELF on vita 74 | -for informing me that dynamic libraries and SceShell export functions. Applications and dynamic libraries import functions. Executables are modules. 75 | -for informing me that plugins are basically dynamic libraries that usually don't export functions 76 | -for informing me that SceShell calls sceCtrlPeekBufferPositive2. Different apps use different control functions. 77 | -for informing me that SceShell is always running in the background 78 | -for informing me to use module_get_offset to use kernel function equivalents to user functions that aren't exported by the kernel (in other words, user functions that aren't listed in the kernel header) 79 | -for informing me that module_get_offset gets the base address of the module and adds the offset of the function to the base address. This result is the address of the function you want to use and you would call it using a function pointer. 80 | -for giving me an example of declaring a function pointer in c. int (*ksceCtrlPeekBufferPositve2)(int, SceCtrlData*, int) = NULL; Pass it to module_get_offset last argument as &ksceCtrlPeekBufferPositive2 81 | -for informing me that sceKernelDelayThread works in function hooks too (but it's bad form and could hurt a tight loop) 82 | -for informing me that segidx argument in module_get_offset is the index of the segment in which the function I'm looking for is. There are 4 segments and usually functions are found in segment 0 83 | -for teaching me how to use callback functions 84 | -for informing me that putting the vita to sleep mode does not run module_stop function 85 | -for informing me that event flags are set by my own code 86 | -for supplying me with sceAVConfigGetSystemVol, sceAVConfigSetSystemVol, sceAVConfigMuteOn offsets 87 | -for directing me to KuromeSan github page for psvita-elfs 88 | -for informing me that AVConfig elf is in bootimage 89 | -for informing me that a module can have multiple stubs 90 | -for teaching me how to understand the code that is decompiled by Ghidra 91 | -for introducing me to a function that determines if the current device is VITA or PSTV 92 | -for informing me that kernel exports and user space syscalls are exported from the same module for most kernel space stuff 93 | -for teaching me about what the segidx parameter is for in module_get_offset function. Functions are put into segment 0. Global variables are put into segment 1. 94 | -for informing me that kernel space and user space share the same global variables 95 | -for informing me that arguments from the function call map linearly in Ghidra 96 | -for informing me that to get SceShell module info for Kernel, you have to wait until SceShell is loaded before calling taiGetModuleInfoForKernel 97 | -for informing me that a function that exists in a stub, but is not in any headers can be declared and used all the same. Stubs are not incomplete and are not missing functions from modules. 98 | 99 | NOTxCorra 100 | -for informing me of header/stub needed for sceClibPrintf function 101 | -for letting me know that caps actually matter 102 | -for helping me setup PrincessLog and getting it to work 103 | -for teaching me that shell/main title id can be referred to as \0 104 | -for teaching me that adding a sceKernelDelayThread in my infinite loop is good practice 105 | -for teaching me that arrays are pointers 106 | -for teaching me to call TAI_CONTINUE first inside function hooks 107 | -for informing me that function export is exported from module. Function import is imported from module. Offset is from base address. 108 | 109 | teakhanirons 110 | -for pointing me to PrincessLog debugging solution 111 | -for teaching me that *ALL in config.txt includes *main 112 | -for teaching me that headers in psp2 are user and psp2kern are kernel 113 | -for pointing me to documentation on thread priority 114 | -for pointing me to examples of kernel and user forms of blit to print to the screen as an overlay 115 | -for confirming that one can use userspace functions in kernel if they get the address of the module in memory and use offsets 116 | -for providing the offset for sceCtrlPeekBufferPositive2 and providing an example using taiHookFunctionOffsetForKernel 117 | -for helping Mer1e with hooking the function offset for sceCtrlPeekBufferPositive2 118 | -for informing me that SceShell calls sceCtrlPeekBufferPositive2 119 | -for suggesting to use a for loop to cycle through each controller in the lightbar_thread code 120 | 121 | Princess of Sleeping 122 | -for teaching me that shell/main title id is referred to as \0, main, or NPXS19999 123 | -for correcting the offsets that Mer1e supplied for sceCtrlGetBatteryInfo and sceCtrlSetLightBar. ghidra returns even addresses when thumb functions are odd so you need to increase the ghidra offset by one byte for thumb functions. 124 | -for informing me that if ksceKernelExitThread is called and then vita goes to sleep, waking from sleep does not auto start threads again. You need to call the startthread function. 125 | -for informing me about waiting with event flags and giving me an example of how it is done 126 | 127 | Mer1e 128 | -for the idea to hook a ksceCtrl function and add SCE_CTRL_VOLUP and SCE_CTRL_VOLDOWN to pad_data->buttons 129 | -for letting me know I needed SceDebugForDriver_stub and sysmem.h header to call ksceDebugPrintf 130 | -for informing me to always call TAI_CONTINUE before any returns 131 | -for informing me to check that TAI_CONTINUE returns a value between 1 and 64 before any code runs inside the hook function 132 | -for providing a working sample of of hooking the function offset for sceCtrlPeekBufferPositive2 133 | -for supplying me with the offsets for sceCtrlGetBatteryInfo and sceCtrlSetLightBar 134 | -for informing me that Kernel plugins are only loaded in one instance by the kernel. User plugins under *MAIN are loaded in one instance under SceShell process. User plugins under *ALL are loaded in separated instances for each of the processes running. 135 | -for informing me that the last argument in module_get_offset should be passed in the form of (uintptr_t*)&ksceCtrlPeekBufferPositive2 136 | 137 | Orangelampshade 138 | -for testing PSTV compatibility 139 | 140 | Rinnegatamante 141 | -for AnalogsEnhancer plugin. I used this as reference for how to open, read, and write to a text file for storing configuration settings. 142 | 143 | cuevavirus 144 | -for Quick Menu Plus plugin. I used this as reference for converting a char array to an integer data type. 145 | 146 | aliihsanasl 147 | -for the idea to give this plugin the ability to change lightbar brightness. 148 | -------------------------------------------------------------------------------- /VolLightbarCtrlWithDS4.yml: -------------------------------------------------------------------------------- 1 | Lightbar: 2 | attributes: 0 3 | version: 4 | major: 1 5 | minor: 3 6 | main: 7 | start: module_start 8 | stop: module_stop 9 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #define R_SUCCEEDED(res) ((res)>=0) 18 | /* BBGGRR <- B=BLUE, G=GREEN, R=RED*/ 19 | #define CYAN 0x00FFFF00 20 | #define MAGENTA 0x00FF00FF 21 | #define YELLOW 0x0000FFFF 22 | #define RED 0x000000FF 23 | #define WHITE 0x00FFFFFF 24 | #define BLANK 0x00000000 25 | #define LIMEGREEN 0x8000FF00 26 | #define GREEN 0x0000FF00 27 | #define SALMON 0xC00000FF 28 | #define SKYBLUE 0x00EE9500 29 | #define BLACK 0xFF000000 30 | #define GRAY 0x00BEBEBE 31 | 32 | #define APP_SYSTEM 0 33 | #define APP_SHELL 1 34 | #define APP_GAME 2 35 | #define APP_PSPEMU 3 36 | 37 | static SceUID tai_uid; 38 | static SceUID tai_uid1; 39 | static tai_hook_ref_t hook; 40 | static tai_hook_ref_t hook1; 41 | static int batteryinfo; 42 | static int lightbarinfo; 43 | int volbeforemute = 100; 44 | SceUInt64 tick_prev_change; 45 | int recentmute = 0; 46 | uint32_t* fb_base; 47 | uint32_t fb_width; 48 | uint32_t fb_height; 49 | uint32_t fb_pitch; 50 | int X1_background; 51 | int X2_background; 52 | int Y1_background; 53 | int Y2_background; 54 | int X1_bar; 55 | int X2_bar; 56 | int Y1_bar; 57 | int Y2_bar; 58 | int Indicator_width; 59 | int X1_indicator; 60 | int X2_indicator; 61 | int Y1_indicator; 62 | int Y2_indicator; 63 | static uint32_t p1multiplier; 64 | static uint32_t p2multiplier; 65 | static uint32_t p3multiplier; 66 | static uint32_t p4multiplier; 67 | int toggle_port1 = 0; 68 | int toggle_port2 = 0; 69 | int toggle_port3 = 0; 70 | int toggle_port4 = 0; 71 | char buffer[3]; 72 | 73 | SceBool ksceAppMgrIsExclusiveProcessRunning(); /*delcare function for determining if current process is exclusive*/ 74 | int ksceSblACMgrIsShell(SceUID pid); /*delcare function for determining if current process is shell (main)*/ 75 | int ksceSblACMgrIsPspEmu(SceUID pid); /*delcare function for determining if current process is PSP Emulation (Adrenaline)*/ 76 | 77 | /*declare function pointers to SceCtrl userland functions*/ 78 | int (*sceCtrlGetBatteryInfo)(int port, SceUInt8 *batt) = NULL; 79 | int (*sceCtrlSetLightBar)(int port, SceUInt8 r, SceUInt8 g, SceUInt8 b) = NULL; 80 | 81 | /*declare function pointer to SceAVConfig global variable which controls master volume*/ 82 | int *mastervol = NULL; 83 | /*declare function pointer to SceAVConfig global variable which holds master volume change event id*/ 84 | int *mastervolchange = NULL; 85 | 86 | /*module_get_offset declaration required because not included in any headers. Part of taihenModuleUtils_stub.*/ 87 | int module_get_offset(SceUID pid, SceUID modid, int segidx, size_t offset, uintptr_t *addr); 88 | 89 | /*Define function for drawing an on-screen volume indicator for PSTV*/ 90 | int showvolumebar(const SceDisplayFrameBuf *framebuffer){ 91 | 92 | fb_base = framebuffer->base; 93 | fb_width = framebuffer->width; 94 | fb_height = framebuffer->height; 95 | fb_pitch = framebuffer->pitch; 96 | 97 | uint32_t color1 = SKYBLUE; 98 | uint32_t color2 = WHITE; 99 | uint32_t color3 = GRAY; 100 | 101 | /*Calculate dimensions of volume bar. Dimensions are relative to size of framebuffer to account for all possible resolutions*/ 102 | X1_background = fb_width / 10; 103 | X2_background = fb_width - X1_background; 104 | Y1_background = 84 * fb_height / 100; 105 | Y2_background = 99 * fb_height / 100; 106 | X1_bar = X1_background + ((X2_background - X1_background) / 10); 107 | X2_bar = X2_background - ((X2_background - X1_background) / 10); 108 | Y1_bar = Y1_background + ((Y2_background - Y1_background) / 3); 109 | Y2_bar = Y2_background - ((Y2_background - Y1_background) / 3); 110 | Indicator_width = 2 * (X2_bar - X1_bar) / 100; 111 | Y1_indicator = Y1_background + (2 * (Y2_background - Y1_background) / 15); 112 | Y2_indicator = Y2_background - (2 * (Y2_background - Y1_background) / 15); 113 | 114 | /*Calculate X1_indicator and X2_indicator based on *mastervol*/ 115 | X1_indicator = X1_bar + ((X2_bar - X1_bar) / 30 * (*mastervol)); 116 | X2_indicator = X1_indicator + Indicator_width; 117 | 118 | /*define arrays to store color value for multiple pixels. This allows for the copying of a color to a row of pixels all at once and drastically improves performance over copying a color to one pixel at a time.*/ 119 | uint32_t line[Indicator_width]; 120 | uint32_t line1[X1_indicator - X1_bar]; 121 | uint32_t line2[X2_bar - X2_indicator]; 122 | 123 | /*Fill first array with WHITE color*/ 124 | for (int i = 0; i < Indicator_width; i++){ 125 | line[i] = color2; 126 | } 127 | 128 | /*Fill second array with SKYBLUE color*/ 129 | for (int j = 0; j < X1_indicator - X1_bar; j++){ 130 | line1[j] = color1; 131 | } 132 | 133 | /*Fill third array with GRAY color*/ 134 | for (int k = 0; k < X2_bar - X2_indicator; k++){ 135 | line2[k] = color3; 136 | } 137 | 138 | /*Draw volume bar one row at a time*/ 139 | for (int y = Y1_indicator; y < Y2_indicator; y++){ 140 | if (y < Y1_bar || y > Y2_bar) { 141 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + X1_indicator], &line[0], sizeof(line)); 142 | } 143 | else { 144 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + X1_bar], &line1[0], sizeof(line1)); 145 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + X1_indicator], &line[0], sizeof(line)); 146 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + X2_indicator], &line2[0], sizeof(line2)); 147 | } 148 | } 149 | 150 | /*Draw volume bar one pixel at a time. Leaving this section as a comment for future reference. 151 | for (int y = Y1_indicator; y < Y2_indicator; y++){ 152 | for (int x = X1_bar; x < X2_bar; x++){ 153 | if (x >= X1_bar && x < X2_bar && y >= Y1_bar && y < Y2_bar){ 154 | if (x >= X1_indicator && x < X2_indicator && y >= Y1_indicator && y < Y2_indicator){ 155 | draw white pixel 156 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + x], &color2, sizeof(color2)); 157 | } 158 | else if (x >= X2_indicator && x < X2_bar && y >= Y1_bar && y < Y2_bar){ 159 | draw gray pixel 160 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + x], &color3, sizeof(color3)); 161 | } 162 | else { 163 | draw green pixel 164 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + x], &color1, sizeof(color1)); 165 | } 166 | don't draw black pixel over green pixels 167 | } 168 | else if (x >= X1_indicator && x < X2_indicator && y >= Y1_indicator && y < Y2_indicator){ 169 | draw white pixel 170 | ksceKernelMemcpyKernelToUser((uintptr_t)&fb_base[y * fb_pitch + x], &color2, sizeof(color2)); 171 | } 172 | } 173 | }*/ 174 | return 0; 175 | } 176 | 177 | /*Reverse Engineered kernel equivalent of sceAVConfigSetMasterVol*/ 178 | void setMasterVolume(int volume){ 179 | *mastervol = volume; 180 | ksceKernelSetEventFlag(*mastervolchange,0x20); 181 | ksceRegMgrSetKeyInt("/CONFIG/SOUND","master_volume",*mastervol); 182 | } 183 | 184 | int ksceDisplaySetFrameBufInternal_patched(int head, int index, const SceDisplayFrameBuf *pParam, int sync) { 185 | 186 | SceUID pid; 187 | char title_id[32] = ""; 188 | int processtype; 189 | 190 | /*Determine process type*/ 191 | pid = ksceKernelGetProcessId(); 192 | ksceKernelGetProcessTitleId(pid, title_id, sizeof(title_id)); 193 | 194 | if (ksceSblACMgrIsPspEmu(pid)){ 195 | processtype = APP_PSPEMU; 196 | } 197 | else if (strncmp(title_id, "NPXS", strlen("NPXS")) == 0){ 198 | processtype = APP_SYSTEM; 199 | } 200 | else if (ksceSblACMgrIsShell(pid)){ 201 | processtype = APP_SHELL; 202 | } 203 | else { 204 | processtype = APP_GAME; 205 | } 206 | 207 | 208 | 209 | if (head != ksceDisplayGetPrimaryHead() || !pParam || !pParam->base){ /*Reduces screen lag quite a bit. Copied from MERLev's reVita plugin.*/ 210 | goto DISPLAY_HOOK_RET; 211 | } 212 | 213 | if (!index && processtype == APP_SHELL){ /*Do not draw on i0 in SceShell. Copied from MERLev's reVita plugin.*/ 214 | goto DISPLAY_HOOK_RET; 215 | } 216 | 217 | if (index && (ksceAppMgrIsExclusiveProcessRunning() || processtype == APP_GAME)){ /*Do not draw over SceShell overlay. Copied from MERLev's reVita plugin.*/ 218 | goto DISPLAY_HOOK_RET; 219 | } 220 | 221 | if (ksceKernelGetSystemTimeWide() - tick_prev_change < 2000000){ /*if less than 2 seconds have gone by since last change in volume, draw volume indicator.*/ 222 | showvolumebar(pParam); 223 | } 224 | DISPLAY_HOOK_RET: 225 | return TAI_CONTINUE(int, hook1, head, index, pParam, sync); 226 | } 227 | 228 | /*sceCtrlPeekBufferPositive2 hook function*/ 229 | int sceCtrlPeekBufferPositive2_patched(int port, SceCtrlData *pad_data, int count) 230 | { 231 | int ret; 232 | int genuineVITA; 233 | int max_volume = 30; 234 | int min_volume = 0; 235 | 236 | /*Should always call TAI_CONTINUE at beginning of hook function */ 237 | ret = TAI_CONTINUE(int, hook, port, pad_data, count); 238 | 239 | /*Make sure TAI_CONTINUE return value is not out of bounds*/ 240 | if (ret < 1 || ret > 64){ 241 | return ret; 242 | } 243 | 244 | /*Check if device is VITA and not PSTV*/ 245 | genuineVITA = ksceSblAimgrIsGenuineVITA(); 246 | 247 | 248 | 249 | /*VITA behavior*/ 250 | /*If L1 and Select are the only buttons pressed, add volume down as a pressed button 251 | If R1 and Select are the only buttons pressed, add volume up as a pressed button 252 | Separate if statements allow for both volume down and volume up to be added as pressed buttons. 253 | Holding volume down and volume up buttons will mute the volume. If L1,L2,R1,R2 are all pressed 254 | and the lightbar brightness is at 100%, lightbar brightness will change to 5%. And vice versa. 255 | This brightness toggle is controller independent, so only the controller that has L1,L2,R1,R2 256 | held down will have its lightbar brightness change.*/ 257 | if (genuineVITA == 1) { 258 | if (pad_data->buttons == (SCE_CTRL_L1 | SCE_CTRL_R1 | SCE_CTRL_LTRIGGER | SCE_CTRL_RTRIGGER)){ 259 | if (port == 1){ 260 | if (toggle_port1 == 0){ 261 | if (p1multiplier == 100){ 262 | p1multiplier = 5; 263 | SceUID fd = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p1config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 264 | ksceIoWrite(fd, "5", 3); 265 | ksceIoClose(fd); 266 | } 267 | else if (p1multiplier == 5){ 268 | p1multiplier = 100; 269 | SceUID fd = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p1config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 270 | ksceIoWrite(fd, "100", 3); 271 | ksceIoClose(fd); 272 | } 273 | toggle_port1 = 1; 274 | } 275 | } 276 | else if (port == 2){ 277 | if (toggle_port2 == 0){ 278 | if (p2multiplier == 100){ 279 | p2multiplier = 5; 280 | SceUID fd1 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p2config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 281 | ksceIoWrite(fd1, "5", 3); 282 | ksceIoClose(fd1); 283 | } 284 | else if (p2multiplier == 5){ 285 | p2multiplier = 100; 286 | SceUID fd1 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p2config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 287 | ksceIoWrite(fd1, "100", 3); 288 | ksceIoClose(fd1); 289 | } 290 | toggle_port2 = 1; 291 | } 292 | } 293 | else if (port == 3){ 294 | if (toggle_port3 == 0){ 295 | if (p3multiplier == 100){ 296 | p3multiplier = 5; 297 | SceUID fd2 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p3config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 298 | ksceIoWrite(fd2, "5", 3); 299 | ksceIoClose(fd2); 300 | } 301 | else if (p3multiplier == 5){ 302 | p3multiplier = 100; 303 | SceUID fd2 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p3config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 304 | ksceIoWrite(fd2, "100", 3); 305 | ksceIoClose(fd2); 306 | } 307 | toggle_port3 = 1; 308 | } 309 | } 310 | else if (port == 4){ 311 | if (toggle_port4 == 0){ 312 | if (p4multiplier == 100){ 313 | p4multiplier = 5; 314 | SceUID fd3 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p4config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 315 | ksceIoWrite(fd3, "5", 3); 316 | ksceIoClose(fd3); 317 | } 318 | else if (p4multiplier == 5){ 319 | p4multiplier = 100; 320 | SceUID fd3 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p4config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 321 | ksceIoWrite(fd3, "100", 3); 322 | ksceIoClose(fd3); 323 | } 324 | toggle_port4 = 1; 325 | } 326 | } 327 | } 328 | else if (pad_data->buttons == (SCE_CTRL_L1 | SCE_CTRL_R1 | SCE_CTRL_SELECT)){ 329 | pad_data->buttons = (SCE_CTRL_VOLDOWN | SCE_CTRL_VOLUP); 330 | } 331 | else if (pad_data->buttons == (SCE_CTRL_L1 | SCE_CTRL_SELECT)){ 332 | pad_data->buttons = SCE_CTRL_VOLDOWN; 333 | } 334 | else if (pad_data->buttons == (SCE_CTRL_R1 | SCE_CTRL_SELECT)){ 335 | pad_data->buttons = SCE_CTRL_VOLUP; 336 | } 337 | else if (pad_data->buttons == 0){ 338 | if (port == 1){ 339 | toggle_port1 = 0; 340 | } 341 | else if (port == 2){ 342 | toggle_port2 = 0; 343 | } 344 | else if (port == 3){ 345 | toggle_port3 = 0; 346 | } 347 | else if (port == 4){ 348 | toggle_port4 = 0; 349 | } 350 | } 351 | } 352 | /*PSTV behavior*/ 353 | /*Volume up and volume down buttons do not work on PSTV. PSTV volume is 354 | controlled by the master volume variable and not the system volume variable. 355 | The code below recreates the volume up and volume down button functionality 356 | using the master volume variable rather than the system volume variable. 357 | If L1,L2,R1,R2 are all pressed and the lightbar brightness is at 100%, lightbar 358 | brightness will change to 5%. And vice versa. This brightness toggle is 359 | controller independent, so only the controller that has L1,L2,R1,R2 360 | held down will have its lightbar brightness change.*/*/ 361 | else { 362 | if (pad_data->buttons == (SCE_CTRL_L1 | SCE_CTRL_R1 | SCE_CTRL_LTRIGGER | SCE_CTRL_RTRIGGER)){ 363 | if (port == 1){ 364 | if (toggle_port1 == 0){ 365 | if (p1multiplier == 100){ 366 | p1multiplier = 5; 367 | SceUID fd = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p1config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 368 | ksceIoWrite(fd, "5", 3); 369 | ksceIoClose(fd); 370 | } 371 | else if (p1multiplier == 5){ 372 | p1multiplier = 100; 373 | SceUID fd = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p1config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 374 | ksceIoWrite(fd, "100", 3); 375 | ksceIoClose(fd); 376 | } 377 | toggle_port1 = 1; 378 | } 379 | } 380 | else if (port == 2){ 381 | if (toggle_port2 == 0){ 382 | if (p2multiplier == 100){ 383 | p2multiplier = 5; 384 | SceUID fd1 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p2config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 385 | ksceIoWrite(fd1, "5", 3); 386 | ksceIoClose(fd1); 387 | } 388 | else if (p2multiplier == 5){ 389 | p2multiplier = 100; 390 | SceUID fd1 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p2config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 391 | ksceIoWrite(fd1, "100", 3); 392 | ksceIoClose(fd1); 393 | } 394 | toggle_port2 = 1; 395 | } 396 | } 397 | else if (port == 3){ 398 | if (toggle_port3 == 0){ 399 | if (p3multiplier == 100){ 400 | p3multiplier = 5; 401 | SceUID fd2 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p3config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 402 | ksceIoWrite(fd2, "5", 3); 403 | ksceIoClose(fd2); 404 | } 405 | else if (p3multiplier == 5){ 406 | p3multiplier = 100; 407 | SceUID fd2 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p3config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 408 | ksceIoWrite(fd2, "100", 3); 409 | ksceIoClose(fd2); 410 | } 411 | toggle_port3 = 1; 412 | } 413 | } 414 | else if (port == 4){ 415 | if (toggle_port4 == 0){ 416 | if (p4multiplier == 100){ 417 | p4multiplier = 5; 418 | SceUID fd3 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p4config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 419 | ksceIoWrite(fd3, "5", 3); 420 | ksceIoClose(fd3); 421 | } 422 | else if (p4multiplier == 5){ 423 | p4multiplier = 100; 424 | SceUID fd3 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p4config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 425 | ksceIoWrite(fd3, "100", 3); 426 | ksceIoClose(fd3); 427 | } 428 | toggle_port4 = 1; 429 | } 430 | } 431 | } 432 | else if (pad_data->buttons == (SCE_CTRL_L1 | SCE_CTRL_R1 | SCE_CTRL_SELECT)){ 433 | if (*mastervol != min_volume) { /*If master volume level is not 0*/ 434 | volbeforemute = *mastervol; /*Store the master volume level into volbeforemute variable*/ 435 | } 436 | setMasterVolume(min_volume); /*Set master volume level to 0*/ 437 | recentmute = 1; /*Let PSTV know that it was recently muted. This prevents the PSTV from unmuting itself if you accidentally release L1 and R1 at different times*/ 438 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Store the precise time that master volume was changed into tick_prev_change variable*/ 439 | pad_data->buttons = 0; /*Limits L1,R1,Select functionality to only muting volume when they are all pressed simultaneously. Example: L1 and R1 will not cause PSTV to switch apps when you are just trying to mute the system.*/ 440 | } 441 | else if (pad_data->buttons == (SCE_CTRL_L1 | SCE_CTRL_SELECT)){ 442 | if (recentmute == 0) { /*If L1,R1,Select buttons have been released since they were last all held down together to mute the PSTV*/ 443 | if (ksceKernelGetSystemTimeWide() - tick_prev_change > 100000) { /*If more than 1/10 of a second has elapsed since the volume was last changed*/ 444 | if (*mastervol != min_volume) { /*If master volume level does not equal 0*/ 445 | if (*mastervol == 1) { /*If master volume level equals 1*/ 446 | volbeforemute = 100; /*This tells PSTV that the volume is 0 because it was lowered rather than muted*/ 447 | } 448 | setMasterVolume(*mastervol - 1); /*decrease the master volume level by 1*/ 449 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Store the precise time that master volume was changed into tick_prev_change variable*/ 450 | } 451 | else if (*mastervol == min_volume) { /*If volume level is 0*/ 452 | if (volbeforemute != 100) { /*If volume level equals 0 because it was muted*/ 453 | setMasterVolume(volbeforemute); /*Set volume level back to what it was before it was muted*/ 454 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Store the precise time that master volume was changed into tick_prev_change variable*/ 455 | } 456 | else if (volbeforemute == 100) { /*If volume equals 0 because it was lowered from 1*/ 457 | /*do not decrease volume any further*/ 458 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Storing current tick into tick_prev_change variable here will allow the volume indicator to keep displaying while Select+L1 are held*/ 459 | } 460 | } 461 | } 462 | } 463 | pad_data->buttons = 0; /*Limits L1,Select functionality to only lowering volume when they are both pressed simultaneously. Example: L1 will not cause PSTV to switch apps when you are just trying to lower the volume.*/ 464 | } 465 | else if (pad_data->buttons == (SCE_CTRL_R1 | SCE_CTRL_SELECT)){ 466 | if (recentmute == 0) { /*If L1,R1,Select buttons have been released since they were last all held down together to mute the PSTV*/ 467 | if (ksceKernelGetSystemTimeWide() - tick_prev_change > 100000) { /*If more than 1/10 of a second has elapsed since the volume was last changed*/ 468 | if (*mastervol == max_volume) { /*If master volume level is 30*/ 469 | /*do not increase volume any further*/ 470 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Storing current tick into tick_prev_change variable here will allow the volume indicator to keep displaying while Select+R1 are held*/ 471 | } 472 | else if (*mastervol != min_volume) { /*If master volume level is not 0*/ 473 | setMasterVolume(*mastervol + 1); /*Increase master volume level by 1*/ 474 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Store the precise time that master volume was changed into tick_prev_change variable*/ 475 | } 476 | else if (*mastervol == min_volume) { /*If volume level is 0*/ 477 | if (volbeforemute != 100) { /*If volume level equals 0 because it was muted*/ 478 | setMasterVolume(volbeforemute); /*Set volume level back to what it was before it was muted*/ 479 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Store the precise time that master volume was changed into tick_prev_change variable*/ 480 | } 481 | else if (volbeforemute == 100) { /*If volume equals 0 because it was lowered from 1*/ 482 | setMasterVolume(*mastervol + 1); /*Increase master volume level by 1*/ 483 | tick_prev_change = ksceKernelGetSystemTimeWide(); /*Store the precise time that master volume was changed into tick_prev_change variable*/ 484 | } 485 | } 486 | } 487 | } 488 | pad_data->buttons = 0; /*Limits R1,Select functionality to only raising volume when they are both pressed simultaneously. Example: R1 will not cause PSTV to switch apps when you are just trying to raise the volume.*/ 489 | } 490 | else if (pad_data->buttons == 0){ 491 | if (port == 1){ 492 | recentmute = 0; 493 | toggle_port1 = 0; 494 | } 495 | else if (port == 2){ 496 | toggle_port2 = 0; 497 | } 498 | else if (port == 3){ 499 | toggle_port3 = 0; 500 | } 501 | else if (port == 4){ 502 | toggle_port4 = 0; 503 | } 504 | } 505 | } 506 | return ret; 507 | } 508 | 509 | /*Check config files for lightbar settings. If none exist, create them.*/ 510 | void loadconfig(void){ 511 | 512 | /*Make folder if it does not exist*/ 513 | ksceIoMkdir("ux0:data/VolLightbarCtrlWithDS4", 0777); 514 | 515 | /*Attempt to open config files for each controller*/ 516 | SceUID fd = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p1config.txt", SCE_O_RDONLY, 0777); 517 | SceUID fd1 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p2config.txt", SCE_O_RDONLY, 0777); 518 | SceUID fd2 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p3config.txt", SCE_O_RDONLY, 0777); 519 | SceUID fd3 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p4config.txt", SCE_O_RDONLY, 0777); 520 | 521 | if (fd >= 0) { 522 | 523 | ksceIoRead(fd, buffer, 3); 524 | ksceIoClose(fd); 525 | p1multiplier = strtol(buffer, NULL, 0); 526 | 527 | } 528 | else { 529 | 530 | fd = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p1config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 531 | ksceIoWrite(fd, "100", 3); 532 | ksceIoClose(fd); 533 | p1multiplier = 100; 534 | 535 | } 536 | if (fd1 >= 0) { 537 | 538 | ksceIoRead(fd1, buffer, 3); 539 | ksceIoClose(fd1); 540 | p2multiplier = strtol(buffer, NULL, 0); 541 | 542 | } 543 | else { 544 | 545 | fd1 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p2config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 546 | ksceIoWrite(fd1, "100", 3); 547 | ksceIoClose(fd1); 548 | p2multiplier = 100; 549 | 550 | } 551 | if (fd2 >= 0) { 552 | 553 | ksceIoRead(fd2, buffer, 3); 554 | ksceIoClose(fd2); 555 | p3multiplier = strtol(buffer, NULL, 0); 556 | 557 | } 558 | else { 559 | 560 | fd2 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p3config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 561 | ksceIoWrite(fd2, "100", 3); 562 | ksceIoClose(fd2); 563 | p3multiplier = 100; 564 | 565 | } 566 | if (fd3 >= 0) { 567 | 568 | ksceIoRead(fd3, buffer, 3); 569 | ksceIoClose(fd3); 570 | p4multiplier = strtol(buffer, NULL, 0); 571 | 572 | } 573 | else { 574 | 575 | fd3 = ksceIoOpen("ux0:/data/VolLightbarCtrlWithDS4/p4config.txt", SCE_O_WRONLY | SCE_O_CREAT, 0777); 576 | ksceIoWrite(fd3, "100", 3); 577 | ksceIoClose(fd3); 578 | p4multiplier = 100; 579 | 580 | } 581 | 582 | } 583 | 584 | /*Thread function to check DS4 battery level and set DS4 lightbar color 585 | to indicate the DS4 battery level. Works with up to four DS4 controllers. 586 | Not sure if this will be the case for everyone... but my DS4 controller dies 587 | at a battery level of 0x2*/ 588 | int lightbar_thread(SceSize arglen, void *arg) { 589 | (void)arglen; 590 | (void)arg; 591 | SceUInt8 battery_level = 0x0; 592 | SceCtrlPortInfo portinfo; 593 | uint32_t lightbarbrightness; 594 | 595 | /*loop runs until vita shuts down*/ 596 | for (;;) { 597 | 598 | /*Check to see how many DS4 controllers are connected*/ 599 | ksceCtrlGetControllerPortInfo(&portinfo); 600 | 601 | /*Check each port. If DS4 is connected to port, check DS4 battery level*/ 602 | for (int c = 1; c < 5; c++) { 603 | if (portinfo.port[c] == 8) { 604 | sceCtrlGetBatteryInfo(c, &battery_level); 605 | if (c == 1) { 606 | lightbarbrightness = p1multiplier; 607 | } 608 | else if (c == 2) { 609 | lightbarbrightness = p2multiplier; 610 | } 611 | else if (c == 3) { 612 | lightbarbrightness = p3multiplier; 613 | } 614 | else if (c == 4) { 615 | lightbarbrightness = p4multiplier; 616 | } 617 | /*If battery level is 0, turn off lightbar*/ 618 | if (battery_level == 0x0) { 619 | sceCtrlSetLightBar(c, 0, 0, 0); 620 | } 621 | /*If battery level is 1, turn off lightbar*/ 622 | else if (battery_level == 0x1) { 623 | sceCtrlSetLightBar(c, 0, 0, 0); 624 | } 625 | /*If battery level is 2, pulse lightbar with red color*/ 626 | else if (battery_level == 0x2) { 627 | for (int i = 0; i<=255; i++) { 628 | sceCtrlSetLightBar(c, i, 0, 0); 629 | ksceKernelDelayThread(3000); 630 | } 631 | for (int i = 255; i>=0; i--) { 632 | sceCtrlSetLightBar(c, i, 0, 0); 633 | ksceKernelDelayThread(3000); 634 | } 635 | } 636 | /*If battery level is 3, turn lightbar red*/ 637 | else if (battery_level == 0x3) { 638 | sceCtrlSetLightBar(c, 255 * lightbarbrightness / 100, 0, 0); 639 | } 640 | /*If battery level is 4, turn lightbar yellow*/ 641 | else if (battery_level == 0x4) { 642 | sceCtrlSetLightBar(c, 255 * lightbarbrightness / 100, 255 * lightbarbrightness / 100, 0); 643 | } 644 | /*If battery level is 5, turn lightbar green*/ 645 | else if (battery_level == 0x5) { 646 | sceCtrlSetLightBar(c, 0, 255 * lightbarbrightness / 100, 0); 647 | } 648 | /*If battery is charging, turn lightbar magenta*/ 649 | else if (battery_level == 0xEE) { 650 | sceCtrlSetLightBar(c, 200 * lightbarbrightness / 100, 0, 255 * lightbarbrightness / 100); 651 | } 652 | /*If battery level is fully charged, turn lightbar turquoise*/ 653 | else if (battery_level == 0xEF) { 654 | sceCtrlSetLightBar(c, 0, 255 * lightbarbrightness / 100, 255 * lightbarbrightness / 100); 655 | } 656 | } 657 | } 658 | /*Add delay in between iterations for stability*/ 659 | ksceKernelDelayThread(100000); 660 | } 661 | return 0; 662 | } 663 | 664 | /*Code execution starts from this point. Loaded by kernel.*/ 665 | int _start() __attribute__ ((weak, alias("module_start"))); 666 | int module_start(SceSize argc, const void *argv) { 667 | 668 | (void)argc; 669 | (void)argv; 670 | 671 | /*Define modInfo for SceCtrl*/ 672 | tai_module_info_t modInfo; 673 | 674 | /*Define modInfo for SceAVConfig*/ 675 | tai_module_info_t modInfo1; 676 | 677 | SceUID thread_id; 678 | 679 | modInfo.size = sizeof(modInfo); 680 | modInfo1.size = sizeof(modInfo1); 681 | 682 | /*Get SceCtrl module info*/ 683 | taiGetModuleInfoForKernel(KERNEL_PID, "SceCtrl", &modInfo); 684 | 685 | /*Get SceAVConfig module info*/ 686 | taiGetModuleInfoForKernel(KERNEL_PID, "SceAVConfig", &modInfo1); 687 | 688 | /*Hook sceCtrlPeekBufferPositive2 function offset (from base address) because kernel does not export sceCtrlPeekBufferPositive2*/ 689 | tai_uid = taiHookFunctionOffsetForKernel(KERNEL_PID, &hook, modInfo.modid, 0, 0x3EF8, 1, sceCtrlPeekBufferPositive2_patched); 690 | 691 | /*Hook ksceDisplaySetFrameBufInternal if device is PSTV*/ 692 | if (!ksceSblAimgrIsGenuineVITA()){ 693 | tai_uid1 = taiHookFunctionExportForKernel(KERNEL_PID, &hook1, "SceDisplay", 0x9FED47AC, 0x16466675, ksceDisplaySetFrameBufInternal_patched); 694 | } 695 | 696 | /*sceCtrlGetBatteryInfo and sceCtrlSetLightBar userland functions are not listed in psp2kern/ctrl.h header. 697 | module_get_offset is called to add the offsets of these functions (0x5E95 and 0x5D81 in this case) to the base address 698 | of the module that they belong to. These resulting addresses belong to the sceCtrlGetBatteryInfo and sceCtrlSetLightBar 699 | userland functions and they are stored in the function pointers so the functions themselves can be used in this kernel plugin.*/ 700 | batteryinfo = module_get_offset(KERNEL_PID, modInfo.modid, 0, 0x5E95, (uintptr_t*)&sceCtrlGetBatteryInfo); 701 | lightbarinfo = module_get_offset(KERNEL_PID, modInfo.modid, 0, 0x5D81, (uintptr_t*)&sceCtrlSetLightBar); 702 | 703 | /*Store address of global variable that controls master volume level in mastervol pointer. Global variables are stored in segidx 1*/ 704 | module_get_offset(KERNEL_PID, modInfo1.modid, 1, 0x280, (uintptr_t*)&mastervol); 705 | /*Store address of global variable that holds master volume change event id in mastervolchange pointer. Global variables are stored in segidx 1*/ 706 | module_get_offset(KERNEL_PID, modInfo1.modid, 1, 0xF4, (uintptr_t*)&mastervolchange); 707 | 708 | /*Create thread to control DS4 lightbar. Highest thread priority is 64. Lowest thread priority is 191.*/ 709 | thread_id = ksceKernelCreateThread("LightbarThread", lightbar_thread, 191, 0x10000, 0, 0, NULL); 710 | 711 | /*Check lightbar settings*/ 712 | loadconfig(); 713 | 714 | /*Start the created thread*/ 715 | ksceKernelStartThread(thread_id, 0, NULL); 716 | 717 | return SCE_KERNEL_START_SUCCESS; 718 | } 719 | 720 | int module_stop(SceSize argc, const void *argv) { (void)argc; (void)argv; 721 | 722 | /*If tai_uid was set to a number greater than or equal to 0, that means the hook 723 | was a success and needs to be released during module_stop*/ 724 | if (R_SUCCEEDED(tai_uid)) 725 | taiHookReleaseForKernel(tai_uid, hook); 726 | 727 | /*If tai_uid1 was set to a number greater than or equal to 0, that means the device is a PSTV and the hook 728 | was a success... so it needs to be released during module_stop*/ 729 | if (R_SUCCEEDED(tai_uid1)) 730 | taiHookReleaseForKernel(tai_uid1, hook1); 731 | 732 | return SCE_KERNEL_STOP_SUCCESS; 733 | } 734 | --------------------------------------------------------------------------------