Advertising (This ad goes away for registered users. You can Login or Register)

OpenIdea Iso Loader

Unleash the full power of your PSP with a Custom Firmware.
Download and installation tutorial at http://wololo.net/wagic/cfw4dummies
Locked
dridri
VIP
Posts: 169
Joined: Wed Oct 27, 2010 5:21 pm

OpenIdea Iso Loader

Post by dridri » Mon Oct 25, 2010 12:36 am

This was initially posted by dridri on advancedpsp.tk
I only retrieved the major post containing code (wololo)


Here the first version, designed for user-exploit, but not working..
[spoiler]Here you can see the actual source code :

isoloader.h

Code: Select all

#include <pspkernel.h>
#include <pspiofilemgr.h>
#include <stdio.h>
#include <stdarg.h>
#include "isomodule.h"

typedef struct Directory {
   int level;
   char name[128];
   char path[1024];
} Directory;

typedef struct File {
   char path[1024];
   char name[256];
   u32 addr;
   u32 size;
   PspIoDrvFileArg* arg;
   int fd;
   int seek_cur;
} File;

typedef struct ISO {
   char filename[2048];
   char name[256];
   Directory* dirs;
   File* files;
   int nDirs;
   int nFiles;
} ISO;

ISO* IsoLoad(SceUID fd);
File* IsoGetFile(ISO* iso, const char* filename);


void GetModuleInfo(SceUID fd);
void GetModuleImports(SceUID fd);
u32 FindImport(u32 text_addr, const char* lib, u32 nid);
void PatchImport(u32 text_addr, const char* lib, u32 nid, u32 patch);


void* geMalloc(int size);
void geFree(void* data);
void* geRealloc(void* last, int size);
void DebugPrint(const char *format, ...);
isoloader.c

Code: Select all

#include <pspiofilemgr.h>
#include "isoloader.h"
#include "debug.h"
void print_to_screen(char*);
void mysprintf1(char *xobuff, const char *xifmt, u32 xidata);

int realKernelLoadModule(const char*, int, void*);
int realKernelStartModule(SceUID, int, void*, int*, void*);
int realKernelQueryModuleInfo(SceUID, void*);

//#define ISO_FILE "ms0:/ISO/wipeoutpulse.iso"
#define ISO_PATH "ms0:/ISO/wipeoutpulse"

int ExtractFile(const char* _src, const char* dst);
void PatchModuleStart();
void prePatch();
void doPatch();
int iso_module_start(int args, void* argp);

ISO* iso = NULL;
SceUID IsoFd;
SceUID modid;
u32 text_addr;

int realIoOpen(const char* _file, int mode, int rights);
int realIoDopen(const char* _dir);
int realIoMkdir(const char* dir, SceMode mode);
int realIoRmdir(const char *dir);
int realIoDevctl(const char* dev, unsigned int cmd, void* indata, int inlen, void* outdata, int outlen);
int realIoGetstat(const char* file, SceIoStat* stat);
int realIoRemove(const char* file);
u32 IoOpen = 0x0;
u32 IoDopen = 0x0;
u32 IoMkdir = 0x0;
u32 IoRmdir = 0x0;
u32 IoDevctl = 0x0;
u32 IoGetstat = 0x0;
u32 IoRemove = 0x0;
u32 KernelLoadModule = 0x0;

void run_iso(){
   get_call_nidtable(0x977DE386, &((u32*)realKernelLoadModule)[1]);
   get_call_nidtable(0x50F0C1EC, &((u32*)realKernelStartModule)[1]);
   get_call_nidtable(0x748CBED9, &((u32*)realKernelQueryModuleInfo)[1]);

   SceUID mod_fd = sceIoOpen("ms0:/ISO/wipeoutpulse/PSP_GAME/SYSDIR/EBOOT_dec.BIN", PSP_O_RDONLY, 777);
   GetModuleInfo(mod_fd);
   GetModuleImports(mod_fd);
   sceIoClose(mod_fd);

   struct SceKernelLMOption option;
   memset((void*)&option, 0x0, sizeof(struct SceKernelLMOption));
   option.size = sizeof(struct SceKernelLMOption);
   option.mpidtext = 2;
   option.mpiddata = 2;
   option.access = 1;
   modid = realKernelLoadModule("ms0:/ISO/wipeoutpulse/PSP_GAME/SYSDIR/EBOOT.BIN", 0, &option);
   LOGSTR1("realKernelLoadModule returned 0x%08lX\n", modid);
   char txt[128] = "";
   mysprintf1(txt, "realKernelLoadModule returned 0x%08lX", modid);
   print_to_screen(txt);
   PatchModuleStart();
   realKernelStartModule(modid, sizeof("disc0:/PSP_GAME/SYSDIR/EBOOT.BIN"), "disc0:/PSP_GAME/SYSDIR/EBOOT.BIN", 0, NULL);

   sceKernelExitDeleteThread(0);
   return;
}

char real_path[512] = "";
int sceIoOpenPatched(const char* _file, int mode, int rights){
   char* file = (char*)_file;
//   LOGSTR3("sceIoOpenPatched(\"%s\", %d, %d)\n", (int)file, mode, rights);
   if(file[0] == 'd'){
      file = file + 6;
      strcpy(real_path, ISO_PATH);
      strcat(real_path, file);
      file = real_path;
   }
//   LOGSTR3("realIoOpen(\"%s\", %d, %d)\n", (int)file, mode, rights);
   return realIoOpen(file, mode, rights);
}

int sceIoDopenPatched(const char* _dir){
   char* dir = (char*)_dir;
//   LOGSTR1("sceIoDopenPatched(\"%s\")\n", (int)dir);
   if(dir[0] == 'd'){
      dir = dir + 6;
      strcpy(real_path, ISO_PATH);
      strcat(real_path, dir);
      dir = real_path;
   }
//   LOGSTR1("realIoDopen(\"%s\")\n", (int)dir);
   return realIoDopen(dir);
}

int sceIoMkdirPatched(const char* dir, SceMode mode){
//   LOGSTR2("realIoMkdir(\"%s\", %d)\n", (int)dir, mode);
   return realIoMkdir(dir, mode);
}

int sceIoRmdirPatched(const char *dir){
//   LOGSTR1("realIoRmdir(\"%s\")\n", (int)dir);
   return realIoRmdir(dir);
}

int sceIoDevctlPatched(const char* dev, unsigned int cmd, void* indata, int inlen, void* outdata, int outlen){
//   LOGSTR8("realIoDevctl(\"%s\", 0x%08lX, 0x%08lX, %d, 0x%08lX, %d)\n", (int)dev, cmd, (int)indata, inlen, (int)outdata, outlen, 0, 0);
   return realIoDevctl(dev, cmd, indata, inlen, outdata, outlen);
}

int sceIoGetstatPatched(const char* _file, SceIoStat* stat){
   char* file = (char*)_file;
//   LOGSTR2("sceIoGetstatPatched(\"%s\", 0x%08lX)\n", (int)file, (u32)stat);
   if(file[0] == 'd'){
      file = file + 6;
      strcpy(real_path, ISO_PATH);
      strcat(real_path, file);
      file = real_path;
   }
//   LOGSTR2("realIoGetstat(\"%s\", 0x%08lX)\n", (int)file, (u32)stat);
   return realIoGetstat(file, stat);
}

int sceIoRemovePatched(const char* file){
//   LOGSTR1("realIoRemove(\"%s\")\n", (int)file);
   return realIoRemove(file);
}

int sceKernelLoadModulePatched(const char* _path, int flags, SceKernelLMOption* option){
   char* path = (char*)_path;
//   LOGSTR3("sceKernelLoadModulePatched(\"%s\")\n", (int)path, flags, (int)option);
   if(path[0] == 'd'){
      path = path + 6;
      strcpy(real_path, ISO_PATH);
      strcat(real_path, path);
      path = real_path;
   }
//   LOGSTR3("realKernelLoadModule(\"%s\")\n", (int)path, flags, (int)option);
   return realKernelLoadModule(path, flags, option);
}

int (*real_module_start)(int, void*) = NULL;
u32 module_start_d0;
u32 module_start_d1;
void PatchModuleStart(){
   // text_addr is always the same
   text_addr = 0x08807C00;

   LOGSTR1("iso.text_addr = 0x%08lX\n", text_addr);
   real_module_start = (void*)text_addr + 0x00000000;

   module_start_d0 = ((u32*)real_module_start)[0];
   module_start_d1 = ((u32*)real_module_start)[1];

   // patch the 2 first opcodes by a jump to my iso_module_start
   ((u32*)real_module_start)[0] = MAKE_JUMP(iso_module_start);
   ((u32*)real_module_start)[1] = 0x00000000;

   prePatch();
}

void prePatch(){
   IoOpen = FindImport(text_addr, "IoFileMgrForUser", 0x109F50BC);
   IoDopen = FindImport(text_addr, "IoFileMgrForUser", 0xB29DDF9C);
   IoMkdir = FindImport(text_addr, "IoFileMgrForUser", 0x06A70004);
   IoRmdir = FindImport(text_addr, "IoFileMgrForUser", 0x1117C65F);
   IoDevctl = FindImport(text_addr, "IoFileMgrForUser", 0x54F5FB11);
   IoGetstat = FindImport(text_addr, "IoFileMgrForUser", 0xACE946E8);
   IoRemove = FindImport(text_addr, "IoFileMgrForUser", 0xF27A9C51);

   KernelLoadModule = FindImport(text_addr, "ModuleMgrForUser", 0x977DE386);
}

int iso_module_start(int args, void* argp){
   // Get the real syscalls
   if(((u32*)realIoOpen)[1] == 0x0){
      ((u32*)realIoOpen)[1] = ((u32*)IoOpen)[1];
      ((u32*)realIoDopen)[1] = ((u32*)IoDopen)[1];
      ((u32*)realIoMkdir)[1] = ((u32*)IoMkdir)[1];
      ((u32*)realIoRmdir)[1] = ((u32*)IoRmdir)[1];
      ((u32*)realIoDevctl)[1] = ((u32*)IoDevctl)[1];
      ((u32*)realIoGetstat)[1] = ((u32*)IoGetstat)[1];
      ((u32*)realIoRemove)[1] = ((u32*)IoRemove)[1];

      ((u32*)realKernelLoadModule)[1] = ((u32*)KernelLoadModule)[1];
   }
   // Then patch them
   _sw(MAKE_JUMP(sceIoOpenPatched), IoOpen); _sw(0x00000000, IoOpen+4);
   _sw(MAKE_JUMP(sceIoDopenPatched), IoDopen); _sw(0x00000000, IoDopen+4);
   _sw(MAKE_JUMP(sceIoMkdirPatched), IoMkdir); _sw(0x00000000, IoMkdir+4);
   _sw(MAKE_JUMP(sceIoRmdirPatched), IoRmdir); _sw(0x00000000, IoRmdir+4);
   _sw(MAKE_JUMP(sceIoDevctlPatched), IoDevctl); _sw(0x00000000, IoDevctl+4);
   _sw(MAKE_JUMP(sceIoGetstatPatched), IoGetstat); _sw(0x00000000, IoGetstat+4);
   _sw(MAKE_JUMP(sceIoRemovePatched), IoRemove); _sw(0x00000000, IoRemove+4);

   _sw(MAKE_JUMP(sceKernelLoadModulePatched), KernelLoadModule); _sw(0x00000000, KernelLoadModule+4);

   // Restore the two first opcodes
   ((u32*)real_module_start)[0] = module_start_d0;
   ((u32*)real_module_start)[1] = module_start_d1;
   return real_module_start(args, argp);
}

static int n_alloc = 0;
static int n_free = 0;

void* geMalloc(int size){
   n_alloc++;
   u32 id = sceKernelAllocPartitionMemory(2, "alloc", 0, size+8, NULL);
   u32* var = (u32*)sceKernelGetBlockHeadAddr(id);
   var[0] = id; //Store the ID in the first byte
   var[1] = size; //Store the size on the 2nd byte
   return var+8; //Then return block +2 (id + size)
}

void geFree(void* data){
   n_free++;
   u32* var = (u32*)data - 2; //Get the real first byte
   u32 id = var[0]; //The get the ID
   sceKernelFreePartitionMemory(id); //We can geFree the data
}

void* geRealloc(void* last, int size){
   u32 old_size = ((u32*)last)[1]; //The size is stored in the 2nd byte
   u32* new_ptr = (u32*)geMalloc(size); //Do a geMalloc with new size
   memcpy(new_ptr, ((u32*)last), old_size); //Copy the lod data
   geFree(last); //geFree the last block
   return new_ptr; //Then return the new pointer
}
isodriver.c (actually unused) :

Code: Select all

#include <pspkernel.h>
#include <pspiofilemgr.h>
#include <stdio.h>
#include "isoloader.h"
#include "debug.h"

int CopyTextToFormatedText(const char* src, char* out, int max);
void UpDir(char* path);
void CheckPath(Directory* dirs, int j);

ISO* IsoLoad(SceUID fd){
   if(fd<0)return NULL;

   int levels[64] = { 0 };
   ISO* iso = (ISO*)geMalloc(sizeof(ISO));
   Directory* dirs = (Directory*)geMalloc(sizeof(Directory)*64);
   memset(dirs, 0, sizeof(Directory)*64);
   File* files = (File*)geMalloc(sizeof(File)*256);

   int i=0, j=0, k=0, a=0, len=0, nFiles=0, pos=0, size=0;
   char name[256], path[1024];
   char* buffer = (char*)geMalloc(sizeof(char)*4096);
   memset(buffer, 0, 4096);

   //Get CD Name
   sceIoLseek(fd, 0x8000, PSP_SEEK_SET);
   sceIoRead(fd, buffer, 128);
   CopyTextToFormatedText(strstr(buffer, "CD001")+7, iso->name, 128);

   //Get Dirs
   memset(buffer, 0, 4096);
   i = 0x9010;
   while(buffer[0]==0){
      sceIoLseek(fd, i, PSP_SEEK_SET);
      sceIoRead(fd, buffer, 1024);
      i += 0x0800;
   }
   for(i=0, j=0; i<1024;){
      memset(levels, 0, sizeof(int)*64);
      dirs[j].level = buffer[i];
      len = CopyTextToFormatedText(buffer+i+2, dirs[j].name, 256);
      CheckPath(dirs, j);
      i += (8 + len);
      for(i=i;((i<1024)&&(buffer[i]==0));i++);
      j++;
      if(buffer[i]+buffer[i+1]+buffer[i+2]+buffer[i+3]+buffer[i+4]+buffer[i+5]+buffer[i+6]+buffer[i+7]==0)break;
   }
   iso->dirs = dirs;
   iso->nDirs = j;

   //Get Files
   j = 0;
   k = 0xB010;
//   LOGSTR0("Files list :\n");
   while(1){
      i = 0x70;
      pos = size = 0;
      sceIoLseek(fd, k, PSP_SEEK_SET);
      sceIoRead(fd, buffer, 1024);
      if(buffer[0]!=0x08)break;
      while(1){
         len = CopyTextToFormatedText(buffer+i+1, name, 256);
         for(a=(i+1+len); buffer[a]==0; a++);
         if(buffer[a]==0x0d){
            if(j>0){
               strcpy(path, dirs[j-1].path);
               strcat(path, name);
            }else{
               strcpy(path, "/");
               strcat(path, name);
            }
            pos = ((u8)buffer[i-30] | (u8)buffer[i-29]<<8 | (u8)buffer[i-28]<<16 | (u8)buffer[i-27]<<24) * 2048;
            size = ((u8)buffer[i-22] | (u8)buffer[i-21]<<8 | (u8)buffer[i-20]<<16 | (u8)buffer[i-19]<<24);
            files[nFiles].addr = pos;
            files[nFiles].size = size;
            files[nFiles].fd = ((int)name - size + (int)path);
            strcpy(files[nFiles].name, name);
            strcpy(files[nFiles].path, path);
//            LOGSTR1(" \"%s\"\n", (int)files[nFiles].path);
            nFiles++;
         }else if((u8)buffer[a]==0x8d){
         }
         i += ((a-i)+42);
         if(buffer[i]+buffer[i+1]+buffer[i+2]+buffer[i+3]+buffer[i+4]+buffer[i+5]+buffer[i+6]+buffer[i+7]==0)break;
      }
      if(/*(k>=0xC9B0)||*/(j>iso->nDirs))break;

      k += 0x0800;
      j++;
   }
   iso->files = files;
   iso->nFiles = nFiles;
   geFree(buffer);
   return iso;
}

File* IsoGetFile(ISO* iso, const char* filename){
   int i = 0;
   for(i=0; i<iso->nFiles; i++){
      if(!strcmp(filename, iso->files[i].path)){
         return &iso->files[i];
      }
   }
   return (File*)0x80010002;
}

int GetParent(Directory* dirs, int j){
   return dirs[j].level-2;
}

void CheckPath(Directory* dirs, int j){
   char DIRS[256][64] = { "" };
   int i=j, parent=j, k=0;
   while(i>=0){
      i = parent;
      parent = GetParent(dirs, i);
      strcpy(DIRS[k], dirs[i].name);
      k++;
   }
   memset(dirs[j].path, 0, 1024);
   strcpy(dirs[j].path, "/");
   for(k=k-2; k>=0; k--){
      strcat(dirs[j].path, DIRS[k]);
      strcat(dirs[j].path, "/");
   }
}

void UpDir(char* path){
   int i = 0;
   for(i=strlen(path); i>=0; i--){
      if((i!=(int)strlen(path))&&(path[i]=='/'))break;
      path[i] = 0;
   }
}

int CopyTextToFormatedText(const char* src, char* out, int max){
   memset(out, 0, max);
   int i = 0;
   int len = 0;
   for(i=0; ((i<max)&&(i<(int)strlen(src))); i++){
      if((src[i]==0) || (src[i]<=0x07) || (src[i]==0x20 && src[i+1]==0x20 && src[i+2]==0x20 && src[i+3]==0x20)){
         break;
      }
      out[i] = src[i];
      len++;
   }
   return len;
}
isoimports.S :

Code: Select all

.macro AddImp funcname

   .globl  \funcname
   .ent    \funcname
   \funcname:
   jr   $ra
   nop
   .end   \funcname

.endm

   .file   1 "isoimports.c"
   .section .mdebug.eabi32
   .section .gcc_compiled_long32
   .previous
   .text
   .align   2

   AddImp sceKernelLoadExec

   AddImp realKernelLoadModule
   AddImp realKernelLoadModuleMs
   AddImp realKernelStartModule
   AddImp realKernelQueryModuleInfo

   AddImp realIoOpen
   AddImp realIoDopen
   AddImp realIoMkdir
   AddImp realIoRmdir
   AddImp realIoDevctl
   AddImp realIoGetstat
   AddImp realIoRemove

   .ident   "ISO-SDK"

@Annon178: It doesn't work on any firmware because of the CheckModulePath

So ! As everybody said, we need a kernel exploit to make something working good.

EDIT: Here a video of the Loader working on CFW (but not using any CFW function!!!): http://img202.imageshack.us/i/mov20100711003.mp4/
To run it on OFW, I need to use the pspSdkInstallNoCheckPath but I can't, I need a kernel exploit.[/spoiler]

Now the loader uses a kernel exploit and works for some games, you can actually try it on 5.00M33, and send me the log if you have psplink.
Converting ISO/CSO into EBOOT tool: OpenIdea EBOOT Builder.zip
Kernel files to put on your MS: OpenIdea IsoLoader for 5.00M33.zip
Advertising
Last edited by dridri on Sun Dec 12, 2010 3:54 pm, edited 1 time in total.
Sorry for my English :mrgreen:
OpenIdea team support: open.idea.team at gmail.com
LibGE

wololo
Site Admin
Posts: 3619
Joined: Wed Oct 15, 2008 12:42 am
Location: Japan

Re: OpenIdea Iso Loader

Post by wololo » Wed Oct 27, 2010 10:39 pm

updated the OP to dridri
Advertising
If you need US PSN Codes, this technique is what I recommend.

Looking for guest bloggers and news hunters here at wololo.net, PM me!

dridri
VIP
Posts: 169
Joined: Wed Oct 27, 2010 5:21 pm

Re: OpenIdea Iso Loader

Post by dridri » Thu Oct 28, 2010 4:54 pm

Thanks wololo.

So, some news here :
The old code was designed for user exploit, this method needs to extract the ISO as a folder and decrypt the EBOOT.BIN: it will be very annoying for many people.
Now, we have a kernel access, then I can use the IoAddDrv and IoMount functions to make a real loader which can read ISO and CSO file types. I also want to add an XMB integration by adding an "ISOs" category in the Game sub-menu.
For the loader itself, I will try to adapt the SystemCtrl_SE isofs and np9660 drivers to the HEN.
Sorry for my English :mrgreen:
OpenIdea team support: open.idea.team at gmail.com
LibGE

RisingACK
Posts: 52
Joined: Sat Oct 16, 2010 7:25 pm

Re: OpenIdea Iso Loader

Post by RisingACK » Thu Oct 28, 2010 9:50 pm

dridri wrote:Thanks wololo.

this method needs to extract the ISO as a folder and decrypt the EBOOT.BIN: it will be very annoying for many people.
Which of the metods needs to extract the ISO as a folder? the old one or the actual one? (yes, dumb question, but I didn't understood how an ISOLoader works.)Also I have aanother question: on CFW everytime that an ISO is loaded the EBOOT.BIN is decrypted on the MS and after that will be ran or is already decrypted with some programs in advanceand after will be running directly
[spoiler]Favorite command:

Code: Select all

make clean && clear && make && cp E* /media/RisingACK/PSP/GAME/HbPathName
[/spoiler]

jaja2u
Posts: 262
Joined: Mon Sep 27, 2010 7:52 pm

Re: OpenIdea Iso Loader

Post by jaja2u » Fri Oct 29, 2010 12:54 am

RisingACK wrote:
dridri wrote:Thanks wololo.

this method needs to extract the ISO as a folder and decrypt the EBOOT.BIN: it will be very annoying for many people.
Which of the metods needs to extract the ISO as a folder? the old one or the actual one? (yes, dumb question, but I didn't understood how an ISOLoader works.)Also I have aanother question: on CFW everytime that an ISO is loaded the EBOOT.BIN is decrypted on the MS and after that will be ran or is already decrypted with some programs in advanceand after will be running directly
I can answer that ;)

The 'usermode' method needs to extract the iso to a folder, and be decrypted already. Usermode does not support the sony iso driver, nor does it support the kernel function, sceKernelLoadModule (or other kernel functions) to decrypt it.

The EBOOT.BIN requires no decryption in advanced for kernel mode, as there are kernel functions, like sceKernelLoadModule, that decrypt it when it loads the module. Thus it's an encrypted EBOOT.BIN on the MS.
Thank you Total_Noob :mrgreen:

Zasisem
Posts: 200
Joined: Mon Sep 27, 2010 3:49 pm

Re: OpenIdea Iso Loader

Post by Zasisem » Fri Oct 29, 2010 2:00 am

so dridri is making an ISO loader? sorry spelled your name wrong but you are?

Mr. X
Retired Mod
Posts: 528
Joined: Tue Sep 28, 2010 8:01 am
Location: England

Re: OpenIdea Iso Loader

Post by Mr. X » Fri Oct 29, 2010 2:11 am

Zasisem wrote:so dridri is making an ISO loader? sorry spelled your name wrong but you are?
http://wololo.net/wagic/2010/10/25/iso- ... on-tn-hen/
Dridri85 on the pspgen forums also said he has a “work in progress” iso loader.
I'm pretty sure he already has an ISO loader, it just needs to be tested for TN's HEN.
All HBL Revisions: Binaries + Source | Topic
TN HEN Releases: Download

RisingACK
Posts: 52
Joined: Sat Oct 16, 2010 7:25 pm

Re: OpenIdea Iso Loader

Post by RisingACK » Fri Oct 29, 2010 9:30 am

So, practicly, the ISO's can be played also in usermode by : extracting EBOOT.BIN-->decrypting it-->putting on MS--> telling to HBL to load it. Am I right?
[spoiler]Favorite command:

Code: Select all

make clean && clear && make && cp E* /media/RisingACK/PSP/GAME/HbPathName
[/spoiler]

dridri
VIP
Posts: 169
Joined: Wed Oct 27, 2010 5:21 pm

Re: OpenIdea Iso Loader

Post by dridri » Fri Oct 29, 2010 9:37 am

RisingACK wrote:So, practicly, the ISO's can be played also in usermode by : extracting EBOOT.BIN-->decrypting it-->putting on MS--> telling to HBL to load it. Am I right?
Yes, you're right.
Sorry for my English :mrgreen:
OpenIdea team support: open.idea.team at gmail.com
LibGE

User avatar
m0skit0
Guru
Posts: 3817
Joined: Mon Sep 27, 2010 6:01 pm

Re: OpenIdea Iso Loader

Post by m0skit0 » Fri Oct 29, 2010 10:05 am

I don't think it's a good idea wasting your time writing an ISO loader with kernel mode since CFW modules already implement that, and a lot of additional features. You're just reinventing the wheel. But anyways, it's a good project to do for learning ;)
I wanna lots of mov al,0xb
Image
"just not into this RA stuffz"

Locked

Return to “Custom Firmwares (HEN/CFW/LCFW)”