aboutsummaryrefslogblamecommitdiff
path: root/taihen-user.c
blob: 03ad1054666e43a673b4a65b98135ac41604b59a (plain) (tree)
1
2
3
4
5
6
7






                                                                






                             
                  

                    
                   

                            



                                             
                         
 
   




























                                                           



                                                                             

                                                                        


                                                                         

                                                                             
                                                                                
                                                                

                                                                               
   



                                                                                    
                              
               



                       
                 
                                                                      
                                    
                                  
                                                 
                                                                                                                   
                                                                                                                                                
                     

                                                                                



                                                      
            
                                  

          
                                             

                                
             






                                                                             

                                                                        


                                                                         

                                                                             

                                                                               
                                                                





                                                                                

                                                                               
   

                                                                                    
                              
               
                       
                  

             
                                                                      
                                    
                                  
                                                 
                                                                                                                   
                                                                                                                                                
                     

                                                                                



                                                      
            
                                  

          
                                             

                                
             







                                                                             

                                                                        


                                                                         

                                                                             
                                                                                
                                                                
   

                                                                                      


                       
             
 
                 
                                                                      
                                    

                                                         


                                                                                                                    

                                                                                




                                                                   

          

                                             
   
             




                                                            






                                                                               

                                        
                                                                   


                                              

                                                                               

                                                                               

                                                                   
               
                              



                           
                                
                                         

                                                                                                           
                                        
                                                                                  
                                                                         





                                  
             


   
                                                     
  
                                      

                                                       
                                        

                                            
                                                                                                  
   
                                                         


                  

                                                   

                                             
                                         


              
             














                                                                         



                       
                                

                                                    
                                           

                             
             







                                                                                
                                    



                                                                         

                                                      



                       
                 
                                                                      
                                    

                                                         
                   


                                                                                                                  
                                               



                                                                   

          

                                             
   
             


   








                                                           


                  

                                                   

                                         
                                         


              
             


   






                                                          

                                                                         
   
                                                                    
                            

             
            
 

                                
                      
                                                                                               
                                 
                                                       
                                
                                          
                       
                                                 

                                   








                                    
             





                                                       
                                   



                                                     

                                                                              
                                                                         
   

                                                                                             
                          


             
            
 

                                
                   
                                                                        
                                      
                                                       
                                                       
                       
                      
                                                                                    
                         
                      
                                     
                                                                                          
                                    
                      
                                                                                
             


                                                                 

              

                                                 






                                   
             




                                               

                                          

                                                          

                                                                         
   

                                                                                   

                            

               
             
            
 

                                
                   
                                                                        

                                        
                                                                                  
                       
                                                                                                   
                                     
                                                                                             
                                    

                                              
                                                     



                                        



                                     

            

                                               

          
                                

             


   

                                                                 






                                                                         
                                                                                   


                            

               
            

 
                              
                   
                                                                        

                                        
                                                                                  
                       
                                                                                                   
                                     
                                                                                                              
                                    

                                              
                                                           



                                        











                                               



             
                                    

                                                 
                                   



                                                     

                                                                                   
   
                                                                                            
                          
                          


             
             
            
 

                                
                   
                                                                        
                                      
                                                       
                                                       
                       
                                                                                    
                         
                      
                                     
                                                                                       
                                    
                      
                                                                                
             
                           
                                                 
             



                                                                 

              
                                     

            

                                               



                                
             






                                                 
                                   

                                            
                                                                         
   
                                                               
             
             
          
            
 

                                
                      
                                                     
                     
                                 
                                                      
                                
                       
                                             



                                                               
       
            




                                   

















                                                                                                  



             
            
 

                                
                   
                                                                        

                                                       
                                                       
                       
                                                                                    

                         
                                     
                                                                                             
                                    
                      
                                                                                

                           
                                                 















                                                                 

















                                                                                            


             
            
 
                              
                   
                                                                        

                                                       
                                                             
                       
                                                                                    

                         
                                     
                                                                                                        
                                    
                      
                                                                                

                           
                                                       















                                                                 













                                                                           

             
            
 
                              
                      
                                                     
                     
                                 
                                                                 
                                
                       
                                             










                                                               

















                                                                                                  


             
            
 
                              
                   
                                                                        

                                                       
                                                             
                       
                                                                                    

                         
                                     
                                                                                                              
                                    
                      
                                                                                

                           
                                                       











                                                                 



                                
             


   











                                                                                                     


             

                                                                                                

                                                                          
                                                                            



                                



             






                                              
                                                                         

                                                                               

          
                              



                                
                 
                                                                             


               













                                              
                                                                         

                                                                               

          
                              



                                
                 
                                                                             


               
 



                                                       

                                                                      
                                            
                                                                         
                                                                    

                           

          
                              
                                         


                                

             
/* taihen-user.c -- user exports for taiHEN
 *
 * Copyright (C) 2016 Yifan Lu
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 */
#include <scetypes.h>
#include <sblacmgr.h>
#include <kernel/cpu.h>
#include <kernel/modulemgr.h>
#include <kernel/sysmem.h>
#include <kernel/threadmgr.h>
#include <kernel/error.h>
#include "error.h"
#include "module.h"
#include "patches.h"
#include "plugin.h"
#include "taihen_internal.h"

/** Limit for strings passed to kernel */
#define MAX_NAME_LEN 256

/** Limit for passing args to start module */
#define MAX_ARGS_SIZE 256

/**
 * @brief      Sets TPIDRURO for a syscall requiring caller
 *             context identification.
 *
 * @return     Previous TPIDRURO value.
 */
__attribute__ ((naked))
static int shift_tpidruro(void) {
  asm (
    "mrc p15, 0, r0, c13, c0, 3\n"
    "lsls r1, r0, 16\n"
    "mcr p15, 0, r1, c13, c0, 3\n"
    "bx lr\n"
  );
}

/**
 * @brief      Restores TPIDRURO value.
 *
 * @param[in]  TPIDRURO value to restore.
 */
__attribute__ ((naked))
static void restore_tpidruro(int v) {
  asm (
    "mcr p15, 0, r0, c13, c0, 3\n"
    "bx lr\n"
  );
}

/**
 * @brief      Add a hook to a module function export for the calling process
 *
 * @see        taiHookFunctionExportForKernel
 *
 * @param[out] p_hook  A reference that can be used by the hook function
 * @param[in]  args    Call arguments
 *
 * @return     A tai patch reference on success, < 0 on error
 *             - TAI_ERROR_PATCH_EXISTS if the address is already patched
 *             - TAI_ERROR_HOOK_ERROR if an internal error occurred trying to
 *               hook
 *             - TAI_ERROR_NOT_IMPLEMENTED if address is in shared memory region
 *             - TAI_ERROR_USER_MEMORY if pointers are incorrect
 *             - TAI_ERROR_INVALID_MODULE if `TAI_MAIN_MODULE` is specified and
 *               there are multiple main modules
 */
SceUID taiHookFunctionExportForUser(tai_hook_ref_t *p_hook, tai_hook_args_t *args) {
  tai_hook_args_t kargs;
  uint32_t func_nid;
  const void *hook_func;
  char k_module[MAX_NAME_LEN];
  int main_mod;
  tai_hook_ref_t k_ref;
  SceUID kid, ret;
  SceUID pid;

  kargs.size = 0;
  sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
  if (kargs.size == sizeof(kargs)) {
    pid = sceKernelGetProcessId();
    main_mod = (kargs.module == TAI_MAIN_MODULE);
    if (main_mod || sceKernelStrncpyUserToKernel(k_module, (uintptr_t)kargs.module, MAX_NAME_LEN) < MAX_NAME_LEN) {
      kid = taiHookFunctionExportForKernel(pid, &k_ref, main_mod ? kargs.module : k_module, kargs.library_nid, kargs.func_nid, kargs.hook_func);
      if (kid >= 0) {
        sceKernelMemcpyKernelToUser((uintptr_t)p_hook, &k_ref, sizeof(*p_hook));
        ret = sceKernelCreateUserUid(pid, kid);
        LOG("kernel uid: %x, user uid: %x", kid, ret);
      } else {
        ret = kid;
      }
    } else {
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    LOG("invalid args size: %x", kargs.size);
    ret = TAI_ERROR_USER_MEMORY;
  }
  return ret;
}

/**
 * @brief      Add a hook to a module function import for the calling process
 *
 * @see        taiHookFunctionImportForKernel
 *
 * @param[out] p_hook  A reference that can be used by the hook function
 * @param[in]  args    Call arguments
 *
 * @return     A tai patch reference on success, < 0 on error
 *             - TAI_ERROR_PATCH_EXISTS if the address is already patched
 *             - TAI_ERROR_HOOK_ERROR if an internal error occurred trying to
 *               hook
 *             - TAI_ERROR_NOT_IMPLEMENTED if address is in shared memory
 *               region. You should hook an import from another module instead.
 *             - TAI_ERROR_USER_MEMORY if pointers are incorrect
 *             - TAI_ERROR_STUB_NOT_RESOLVED if the import has not been resolved
 *               yet. You should hook `sceKernelLoadStartModule`,
 *               `sceSysmoduleLoadModule` or whatever the application uses to
 *               start the imported module and add this hook after the module is
 *               loaded. Be sure to also hook module unloading to remove the
 *               hook BEFORE the imported module is unloaded!
 *             - TAI_ERROR_INVALID_MODULE if `TAI_MAIN_MODULE` is specified and
 *               there are multiple main modules
 */
SceUID taiHookFunctionImportForUser(tai_hook_ref_t *p_hook, tai_hook_args_t *args) {
  tai_hook_args_t kargs;
  char k_module[MAX_NAME_LEN];
  int main_mod;
  tai_hook_ref_t k_ref;
  SceUID kid, ret;
  SceUID pid;

  sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
  if (kargs.size == sizeof(kargs)) {
    pid = sceKernelGetProcessId();
    main_mod = (kargs.module == TAI_MAIN_MODULE);
    if (main_mod || sceKernelStrncpyUserToKernel(k_module, (uintptr_t)kargs.module, MAX_NAME_LEN) < MAX_NAME_LEN) {
      kid = taiHookFunctionImportForKernel(pid, &k_ref, main_mod ? kargs.module : k_module, kargs.library_nid, kargs.func_nid, kargs.hook_func);
      if (kid >= 0) {
        sceKernelMemcpyKernelToUser((uintptr_t)p_hook, &k_ref, sizeof(*p_hook));
        ret = sceKernelCreateUserUid(pid, kid);
        LOG("kernel uid: %x, user uid: %x", kid, ret);
      } else {
        ret = kid;
      }
    } else {
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    LOG("invalid args size: %x", kargs.size);
    ret = TAI_ERROR_USER_MEMORY;
  }
  return ret;
}

/**
 * @brief      Add a hook to a module manually with an offset for the calling
 *             process
 *
 * @see        taiHookFunctionOffsetForKernel
 *
 * @param[out] p_hook  A reference that can be used by the hook function
 * @param[in]  args    Call arguments
 *
 * @return     A tai patch reference on success, < 0 on error
 *             - TAI_ERROR_PATCH_EXISTS if the address is already patched
 *             - TAI_ERROR_HOOK_ERROR if an internal error occurred trying to
 *               hook
 *             - TAI_ERROR_NOT_IMPLEMENTED if address is in shared memory region
 *             - TAI_ERROR_USER_MEMORY if pointers are incorrect
 */
SceUID taiHookFunctionOffsetForUser(tai_hook_ref_t *p_hook, tai_offset_args_t *args) {
  tai_offset_args_t kargs;
  tai_hook_ref_t k_ref;
  SceUID ret;
  SceUID pid;
  SceUID kid;

  kargs.size = 0;
  sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
  if (kargs.size == sizeof(kargs)) {
    pid = sceKernelGetProcessId();
    kid = sceKernelKernelUidForUserUid(pid, kargs.modid);
    if (kid >= 0) {
      ret = taiHookFunctionOffsetForKernel(pid, &k_ref, kid, kargs.segidx, kargs.offset, kargs.thumb, kargs.source);
      if (ret >= 0) {
        sceKernelMemcpyKernelToUser((uintptr_t)p_hook, &k_ref, sizeof(*p_hook));
        ret = sceKernelCreateUserUid(pid, ret);
        LOG("user uid: %x", ret);
      }
    } else {
      LOG("Error getting kernel uid for %x: %x", kargs.modid, kid);
      ret = kid;
    }
  } else {
    LOG("invalid args size: %x", kargs.size);
    ret = TAI_ERROR_USER_MEMORY;
  }
  return ret;
}

/**
 * @brief      Gets information on a currently loaded module
 *
 *             You can use the macro `TAI_MAIN_MODULE` for `module` to specify
 *             the main module. This is usually the module that is loaded first
 *             and is usually the eboot.bin. This will only work if there is
 *             only one module loaded in the main memory space. Not all
 *             processes have this property! Make sure you check the return
 *             value.
 *
 * @see        taiGetModuleInfoForKernel
 *
 * @param[in]  module  The name of the module or `TAI_MAIN_MODULE`.
 * @param[out] info    The information to fill
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_USER_MEMORY if `info->size` is too small or large or
 *               `module` is invalid
 *             - TAI_ERROR_INVALID_MODULE if `TAI_MAIN_MODULE` is specified and
 *               there are multiple main modules
 */
int taiGetModuleInfo(const char *module, tai_module_info_t *info) {
  int main_mod;
  char k_module[MAX_NAME_LEN];
  SceUID pid;
  tai_module_info_t k_info;
  int ret;

  pid = sceKernelGetProcessId();
  main_mod = (module == TAI_MAIN_MODULE);
  if (main_mod || sceKernelStrncpyUserToKernel(k_module, (uintptr_t)module, MAX_NAME_LEN) < MAX_NAME_LEN) {
    sceKernelMemcpyUserToKernel(&k_info, (uintptr_t)info, sizeof(size_t));
    if (k_info.size == sizeof(k_info)) {
      ret = taiGetModuleInfoForKernel(pid, main_mod ? module : k_module, &k_info);
      sceKernelMemcpyKernelToUser((uintptr_t)info, &k_info, k_info.size);
    } else {
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_USER_MEMORY;
  }
  return ret;
}

/**
 * @brief      Release a hook for the calling process
 *
 * @see        taiHookReleaseForKernel
 *
 * @param[in]  tai_uid  The tai patch reference to free
 * @param[in]  hook     The hook to free
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_HOOK_ERROR if an internal error occurred trying to restore the function
 */
int taiHookRelease(SceUID tai_uid, tai_hook_ref_t hook) {
  SceUID pid, kid;
  int ret;

  pid = sceKernelGetProcessId();
  kid = sceKernelKernelUidForUserUid(pid, tai_uid);
  if (kid >= 0) {
    ret = taiHookReleaseForKernel(kid, hook);
    sceKernelDeleteUserUid(pid, tai_uid);
  } else {
    ret = kid;
  }
  return ret;
}

/**
 * @brief      Injects data into the current process bypassing MMU flags
 * 
 * @see taiInjectAbsForKernel
 *
 * @param      dest  The address to inject
 * @param[in]  src   Source data
 * @param[in]  size  The size of the injection in bytes
 *
 * @return     A tai patch reference on success, < 0 on error
 *             - TAI_ERROR_PATCH_EXISTS if the address is already patched
 */
SceUID taiInjectAbs(void *dest, const void *src, size_t size) {
  tai_hook_ref_t k_ref;
  SceUID ret;
  SceUID pid;

  pid = sceKernelGetProcessId();
  ret = taiInjectAbsForKernel(pid, dest, src, size);
  if (ret >= 0) {
    ret = sceKernelCreateUserUid(pid, ret);
    LOG("user uid: %x", ret);
  }
  return ret;
}

/**
 * @brief      Inject data into the current process bypassing MMU flags given an
 *             offset
 *
 * @see        taiInjectDataForKernel
 *
 * @param[in]  args   Call arguments
 *
 * @return     A tai patch reference on success, < 0 on error
 *             - TAI_ERROR_PATCH_EXISTS if the address is already patched
 */
SceUID taiInjectDataForUser(tai_offset_args_t *args) {
  tai_offset_args_t kargs;
  tai_hook_ref_t k_ref;
  SceUID ret;
  SceUID pid;

  kargs.size = 0;
  sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
  if (kargs.size == sizeof(kargs)) {
    pid = sceKernelGetProcessId();
    ret = sceKernelKernelUidForUserUid(pid, kargs.modid);
    if (ret >= 0) {
      kargs.modid = ret;
      ret = taiInjectDataForKernel(pid, kargs.modid, kargs.segidx, kargs.offset, kargs.source, kargs.source_size);
      if (ret >= 0) {
        ret = sceKernelCreateUserUid(pid, ret);
        LOG("user uid: %x", ret);
      }
    } else {
      LOG("Error getting kernel uid for %x: %x", kargs.modid, ret);
    }
  } else {
    LOG("invalid args size: %x", kargs.size);
    ret = TAI_ERROR_USER_MEMORY;
  }
  return ret;
}

/**
 * @brief      Release an injection for the calling process
 *
 * @see        taiInjectReleaseForKernel
 *
 * @param[in]  tai_uid  The tai patch reference to free
 *
 * @return     Zero on success, < 0 on error
 */
int taiInjectRelease(SceUID tai_uid) {
  SceUID pid, kid;
  int ret;

  pid = sceKernelGetProcessId();
  kid = sceKernelKernelUidForUserUid(pid, tai_uid);
  if (kid >= 0) {
    ret = taiInjectReleaseForKernel(kid);
    sceKernelDeleteUserUid(pid, tai_uid);
  } else {
    ret = kid;
  }
  return ret;
}

/**
 * @brief      Loads a kernel module
 *
 * @param[in]  path   The path to the skprx
 * @param[in]  flags  The flags
 * @param      opt    Optional arguments, set to NULL
 *
 * @return     A module reference on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `opt` is not NULL
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
SceUID taiLoadKernelModule(const char *path, int flags, void *opt) {
  char k_path[MAX_NAME_LEN];
  SceUID pid;
  int ret;
  int state;

  pid = sceKernelGetProcessId();
  if (sceSblACMgrIsShell(0)) {
    if (opt == NULL) {
      if (sceKernelStrncpyUserToKernel(k_path, (uintptr_t)path, MAX_NAME_LEN) < MAX_NAME_LEN) {
        state = shift_tpidruro();
        ret = sceKernelLoadModule(k_path, flags, NULL);
        restore_tpidruro(state);
        LOG("loaded %s: %x", k_path, ret);
        if (ret >= 0) {
          ret = sceKernelCreateUserUid(pid, ret);
          LOG("user uid: %x", ret);
        }
      } else {
        ret = TAI_ERROR_USER_MEMORY;
      }
    } else {
      ret = TAI_ERROR_INVALID_ARGS;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Starts a kernel module
 *
 * @param[in]  modid  The id from `taiLoadKernelModule`
 * @param[in]  args   The arguments
 * @param      opt    Optional arguments, set to NULL
 * @param      res    Return value of `module_start`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large or `opt` is not
 *               NULL
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiStartKernelModuleForUser(SceUID modid, tai_module_args_t *args, void *opt, int *res) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  int ret;
  int k_res;
  SceUID pid;
  int state;

  pid = sceKernelGetProcessId();
  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE && opt == NULL) {
        ret = sceKernelKernelUidForUserUid(pid, modid);
        if (ret >= 0) {
          modid = ret;
          ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
          if (ret >= 0) {
            k_res = 0;
            state = shift_tpidruro();
            ret = sceKernelStartModule(modid, kargs.args, buf, kargs.flags, NULL, &k_res);
            restore_tpidruro(state);
            if (res) {
              sceKernelMemcpyKernelToUser((uintptr_t)res, &k_res, sizeof(*res));
            }
          }
        } else {
          LOG("Error getting kernel uid for %x: %x", modid, ret);
        }
      } else {
        LOG("invalid args size: %x", kargs.size);
        ret = TAI_ERROR_USER_MEMORY;
      }
    } else {
      ret = TAI_ERROR_INVALID_ARGS;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Loads and starts a kernel module
 *
 * @param[in]  path  The path of the skprx
 * @param[in]  args  The arguments
 *
 * @return     A module reference on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
SceUID taiLoadStartKernelModuleForUser(const char *path, tai_module_args_t *args) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  char k_path[MAX_NAME_LEN];
  SceUID modid;
  int ret;
  SceUID pid;
  int state;

  pid = sceKernelGetProcessId();
  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE) {
        ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
        if (ret >= 0) {
          if (sceKernelStrncpyUserToKernel(k_path, (uintptr_t)path, MAX_NAME_LEN) < MAX_NAME_LEN) {
            state = shift_tpidruro();
            ret = sceKernelLoadStartModule(k_path, kargs.args, buf, kargs.flags, NULL, NULL);
            restore_tpidruro(state);
            LOG("loaded %s: %x", k_path, ret);
            if (ret >= 0) {
              ret = sceKernelCreateUserUid(pid, ret);
              LOG("user uid: %x", ret);
            }
          } else {
            ret = TAI_ERROR_USER_MEMORY;
          }
        }
      } else {
        ret = TAI_ERROR_INVALID_ARGS;
      }
    } else {
      LOG("invalid args size: %x", kargs.size);
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Loads and starts a user module for another process
 *
 * @param[in]  path  The path of the skprx
 * @param[in]  args  The arguments
 *
 * @return     A module reference on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
SceUID taiLoadStartModuleForPidForUser(const char *path, tai_module_args_t *args) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  char k_path[MAX_NAME_LEN];
  SceUID modid;
  int ret;
  int state;


  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE) {
        ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
        if (ret >= 0) {
          if (sceKernelStrncpyUserToKernel(k_path, (uintptr_t)path, MAX_NAME_LEN) < MAX_NAME_LEN) {
            state = shift_tpidruro();
            ret = sceKernelLoadStartModuleForPid(kargs.pid, k_path, kargs.args, buf, kargs.flags, NULL, NULL);
            restore_tpidruro(state);
            LOG("loaded %s: %x", k_path, ret);
            if (ret >= 0) {
              ret = sceKernelCreateUserUid(kargs.pid, ret);
              LOG("user uid: %x", ret);
            }
          } else {
            ret = TAI_ERROR_USER_MEMORY;
          }
        }
      } else {
        ret = TAI_ERROR_INVALID_ARGS;
      }
    } else {
      LOG("invalid args size: %x", kargs.size);
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Stops a kernel module
 *
 * @param[in]  modid  The loaded module reference
 * @param[in]  args   The arguments
 * @param      opt    Optional arguments, set to NULL
 * @param      res    Return value of `module_stop`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large or `opt` is not NULL
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiStopKernelModuleForUser(SceUID modid, tai_module_args_t *args, void *opt, int *res) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  int ret;
  int k_res;
  SceUID pid;
  SceUID kid;
  int state;

  pid = sceKernelGetProcessId();
  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE && opt == NULL) {
        kid = sceKernelKernelUidForUserUid(pid, modid);
        if (kid >= 0) {
          ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
          if (ret >= 0) {
            k_res = 0;
            state = shift_tpidruro();
            ret = sceKernelStopModule(kid, kargs.args, buf, kargs.flags, NULL, &k_res);
            restore_tpidruro(state);
            if (res) {
              sceKernelMemcpyKernelToUser((uintptr_t)res, &k_res, sizeof(*res));
            }
            if (ret >= 0) {
              sceKernelDeleteUserUid(pid, modid);
            }
          }
        } else {
          LOG("Error getting kernel uid for %x: %x", modid, kid);
          ret = kid;
        }
      } else {
        ret = TAI_ERROR_INVALID_ARGS;
      }
    } else {
      LOG("invalid args size: %x", kargs.size);
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Unloads a kernel module directly
 *
 * @param[in]  modid  The loaded module reference
 * @param[in]  flags  The flags
 * @param      opt    Set to `NULL`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiUnloadKernelModule(SceUID modid, int flags, void *opt) {
  SceUID pid;
  SceUID kid;
  int ret;
  int state;

  pid = sceKernelGetProcessId();
  if (sceSblACMgrIsShell(0)) {
    if (opt == NULL) {
      kid = sceKernelKernelUidForUserUid(pid, modid);
      if (kid >= 0) {
        state = shift_tpidruro();
        ret = sceKernelUnloadModule(kid, flags, NULL);
        restore_tpidruro(state);
        if (ret >= 0) {
          sceKernelDeleteUserUid(pid, modid);
        }
      } else {
        LOG("Error getting kernel uid for %x: %x", modid, kid);
        ret = kid;
      }
    } else {
      ret = TAI_ERROR_INVALID_ARGS;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Stops and unloads a kernel module
 *
 * @param[in]  modid  The loaded module reference
 * @param[in]  args   The arguments
 * @param      opt    Optional arguments, set to NULL
 * @param      res    Return value of `module_stop`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large or `opt` is not NULL
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiStopUnloadKernelModuleForUser(SceUID modid, tai_module_args_t *args, void *opt, int *res) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  int ret;
  int k_res;
  SceUID pid;
  SceUID kid;
  int state;

  pid = sceKernelGetProcessId();
  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE && opt == NULL) {
        kid = sceKernelKernelUidForUserUid(pid, modid);
        if (kid >= 0) {
          ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
          if (ret >= 0) {
            k_res = 0;
            state = shift_tpidruro();
            ret = sceKernelStopUnloadModule(kid, kargs.args, buf, kargs.flags, NULL, &k_res);
            restore_tpidruro(state);
            if (res) {
              sceKernelMemcpyKernelToUser((uintptr_t)res, &k_res, sizeof(*res));
            }
            if (ret >= 0) {
              sceKernelDeleteUserUid(pid, modid);
            }
          }
        } else {
          LOG("Error getting kernel uid for %x: %x", modid, kid);
          ret = kid;
        }
      } else {
        ret = TAI_ERROR_INVALID_ARGS;
      }
    } else {
      LOG("invalid args size: %x", kargs.size);
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Stops a user module for another process
 *
 * @param[in]  modid  The loaded module reference
 * @param[in]  args   The arguments
 * @param      opt    Optional arguments, set to NULL
 * @param      res    Return value of `module_stop`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large or `opt` is not NULL
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiStopModuleForPidForUser(SceUID modid, tai_module_args_t *args, void *opt, int *res) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  int ret;
  int k_res;
  SceUID kid;
  int state;

  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE && opt == NULL) {
        kid = sceKernelKernelUidForUserUid(kargs.pid, modid);
        if (kid >= 0) {
          ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
          if (ret >= 0) {
            k_res = 0;
            state = shift_tpidruro();
            ret = sceKernelStopModuleForPid(kargs.pid, kid, kargs.args, buf, kargs.flags, NULL, &k_res);
            restore_tpidruro(state);
            if (res) {
              sceKernelMemcpyKernelToUser((uintptr_t)res, &k_res, sizeof(*res));
            }
            if (ret >= 0) {
              sceKernelDeleteUserUid(kargs.pid, modid);
            }
          }
        } else {
          LOG("Error getting kernel uid for %x: %x", modid, kid);
          ret = kid;
        }
      } else {
        ret = TAI_ERROR_INVALID_ARGS;
      }
    } else {
      LOG("invalid args size: %x", kargs.size);
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Unloads a user module for a process directly
 *
 * @param[in]  modid  The loaded module reference
 * @param[in]  flags  The flags
 * @param      opt    Set to `NULL`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiUnloadModuleForPid(SceUID pid, SceUID modid, int flags, void *opt) {
  SceUID kid;
  int ret;
  int state;

  if (sceSblACMgrIsShell(0)) {
    if (opt == NULL) {
      kid = sceKernelKernelUidForUserUid(pid, modid);
      if (kid >= 0) {
        state = shift_tpidruro();
        ret = sceKernelUnloadModuleForPid(pid, kid, flags, NULL);
        restore_tpidruro(state);
        if (ret >= 0) {
          sceKernelDeleteUserUid(pid, modid);
        }
      } else {
        LOG("Error getting kernel uid for %x: %x", modid, kid);
        ret = kid;
      }
    } else {
      ret = TAI_ERROR_INVALID_ARGS;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Stops and unloads a user module for a process
 *
 * @param[in]  modid  The loaded module reference
 * @param[in]  args   The arguments
 * @param      opt    Optional arguments, set to NULL
 * @param      res    Return value of `module_stop`
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_INVALID_ARGS if `args` is too large or `opt` is not NULL
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiStopUnloadModuleForPidForUser(SceUID modid, tai_module_args_t *args, void *opt, int *res) {
  tai_module_args_t kargs;
  char buf[MAX_ARGS_SIZE];
  int ret;
  int k_res;
  SceUID kid;
  int state;

  if (sceSblACMgrIsShell(0)) {
    kargs.size = 0;
    sceKernelMemcpyUserToKernel(&kargs, (uintptr_t)args, sizeof(kargs));
    if (kargs.size == sizeof(kargs)) {
      if (kargs.args <= MAX_ARGS_SIZE && opt == NULL) {
        kid = sceKernelKernelUidForUserUid(kargs.pid, modid);
        if (kid >= 0) {
          ret = sceKernelMemcpyUserToKernel(buf, (uintptr_t)kargs.argp, kargs.args);
          if (ret >= 0) {
            k_res = 0;
            state = shift_tpidruro();
            ret = sceKernelStopUnloadModuleForPid(kargs.pid, kid, kargs.args, buf, kargs.flags, NULL, &k_res);
            restore_tpidruro(state);
            if (res) {
              sceKernelMemcpyKernelToUser((uintptr_t)res, &k_res, sizeof(*res));
            }
            if (ret >= 0) {
              sceKernelDeleteUserUid(kargs.pid, modid);
            }
          }
        } else {
          LOG("Error getting kernel uid for %x: %x", modid, kid);
          ret = kid;
        }
      } else {
        ret = TAI_ERROR_INVALID_ARGS;
      }
    } else {
      LOG("invalid args size: %x", kargs.size);
      ret = TAI_ERROR_USER_MEMORY;
    }
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}

/**
 * @brief      Gets an exported function address for a module of the calling process
 *
 * @param[in]  modname  The name of module to lookup
 * @param[in]  libnid   NID of the exporting library. Can be `TAI_ANY_LIBRARY`.
 * @param[in]  funcnid  NID of the exported function
 * @param[out] func     Output address of the function
 *
 * @return     Zero on success, < 0 on error
 */
int taiGetModuleExportFunc(const char *modname, uint32_t libnid, uint32_t funcnid, uintptr_t *func) {
  char k_module[MAX_NAME_LEN];
  uintptr_t k_func;
  SceUID pid;
  int ret;

  pid = sceKernelGetProcessId();
  if (sceKernelStrncpyUserToKernel(k_module, (uintptr_t)modname, MAX_NAME_LEN) < MAX_NAME_LEN) {
    ret = module_get_export_func(pid, k_module, libnid, funcnid, &k_func);
    if (ret == 0) {
      sceKernelMemcpyKernelToUser((uintptr_t)func, &k_func, sizeof(k_func));
    }
  } else {
    ret = TAI_ERROR_USER_MEMORY;
  }
  return ret;
}

/**
 * @brief      Copies data from user to kernel
 *
 * @param      kernel_dst  The kernel address
 * @param[in]  user_src    The user address
 * @param[in]  len         The length
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiMemcpyUserToKernel(void *kernel_dst, const void *user_src, size_t len) {
  int ret;

  if (sceSblACMgrIsShell(0)) {
    ret = 0;
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  if (ret == 0) {
    return sceKernelMemcpyUserToKernel(kernel_dst, (uintptr_t)user_src, len);
  } else {
    return ret;
  }
}

/**
 * @brief      Copies data from kernel to user
 *
 *             Does not bypass the MMU!
 *
 * @see        taiInjectData
 *
 * @param      user_dst    The user address
 * @param[in]  kernel_src  The kernel address
 * @param[in]  len         The length
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 */
int taiMemcpyKernelToUser(void *user_dst, const void *kernel_src, size_t len) {
  int ret;

  if (sceSblACMgrIsShell(0)) {
    ret = 0;
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  if (ret == 0) {
    return sceKernelMemcpyKernelToUser((uintptr_t)user_dst, kernel_src, len);
  } else {
    return ret;
  }
}

/**
 * @brief      Reloads config.txt from the default path
 *
 *             Note this cannot be called from a plugin start handler!
 *
 * @return     Zero on success, < 0 on error
 *             - TAI_ERROR_NOT_ALLOWED if caller does not have permission
 *             - TAI_ERROR_BLOCKING if attempted to call recursively
 */
int taiReloadConfig(void) {
  int ret;

  if (sceSblACMgrIsShell(0)) {
    ret = taiReloadConfigForKernel(0, 0);
  } else {
    ret = TAI_ERROR_NOT_ALLOWED;
  }
  return ret;
}