Compare commits
24 Commits
1.0.1
...
modmii-edi
Author | SHA1 | Date |
---|---|---|
Naim2000 | d9ba968f90 | |
Naim2000 | e49d9a8850 | |
Naim2000 | 7cc66cc0a1 | |
Naim2000 | 7e86d9ce5f | |
Naim2000 | 4875edf350 | |
Naim2000 | 66a8fb1440 | |
Naim2000 | 7b47e2800e | |
Naim2000 | 60c42a760c | |
Naim2000 | d18498361e | |
Naim2000 | 34aae016b9 | |
Naim2000 | d5a817b680 | |
thepikachugamer | 1a825dcfce | |
thepikachugamer | 608d954afe | |
MikeIsAStar | 13dd003f3d | |
Naim2000 | bc99919c45 | |
Naim2000 | 53072c83fe | |
Naim2000 | 289a1cee79 | |
thepikachugamer | 18f2e3f07f | |
Naim2000 | 5283493c4e | |
Naim2000 | 1d0f7d6f67 | |
thepikachugamer | cb2351b877 | |
thepikachugamer | abd5f48be9 | |
thepikachugamer | 8c0269a8bc | |
XFlak | b520bc7f88 |
4
Makefile
4
Makefile
|
@ -21,7 +21,7 @@ include $(DEVKITPPC)/wii_rules
|
|||
TARGET := boot
|
||||
BUILD := build
|
||||
SOURCES := source include source/libtinysmb source/libpng source/libpng/pngu
|
||||
DATA := data
|
||||
DATA := data
|
||||
INCLUDES :=
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
@ -36,7 +36,7 @@ LDFLAGS = $(MACHDEP) -Wl,-Map,$(notdir $@).map
|
|||
#---------------------------------------------------------------------------------
|
||||
# any extra libraries we wish to link with the project
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBS := -lmxml -ltinysmb -lpng -lfat -lwiidrc -lwiiuse -lbte -logc -lm -lz -lwiilight
|
||||
LIBS := -lmxml -ltinysmb -lpng -lfat -lwiikeyboard -lwiidrc -lwiiuse -lbte -logc -lm -lz -lwiilight
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*-------------------------------------------------------------
|
||||
|
||||
aes.c -- AES Engine
|
||||
|
||||
Copyright (C) 2022
|
||||
GaryOderNichts
|
||||
Joris 'DacoTaco' Vermeylen info@dacotaco.com
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you
|
||||
must not claim that you wrote the original software. If you use
|
||||
this software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
-------------------------------------------------------------*/
|
||||
#if defined(HW_RVL)
|
||||
|
||||
#include <string.h>
|
||||
#include <gctypes.h>
|
||||
#include <gcutil.h>
|
||||
#include <ogc/ipc.h>
|
||||
|
||||
#include "aes.h"
|
||||
|
||||
#define AES_HEAPSIZE 0x200
|
||||
|
||||
#define AES_IOCTLV_ENCRYPT 2
|
||||
#define AES_IOCTLV_DECRYPT 3
|
||||
|
||||
static s32 __aes_fd = -1;
|
||||
static s32 __aes_hid = -1;
|
||||
|
||||
static s32 AES_ExecuteCommand(s32 command, const void* key, u32 key_size, void* iv, u32 iv_size, const void* in_data, void* out_data, u32 data_size)
|
||||
{
|
||||
if ((((u32)in_data | (u32)out_data) & 0xF) != 0)
|
||||
return -4;
|
||||
|
||||
if (key_size != 16 || iv_size != 16 || (data_size & 15) != 0)
|
||||
return -4;
|
||||
|
||||
ioctlv* params = (ioctlv*)iosAlloc(__aes_hid, sizeof(ioctlv) * 4);
|
||||
if (!params)
|
||||
return -1;
|
||||
|
||||
s32 ret = -1;
|
||||
for (u32 i = 0; i < data_size; i += AES_BLOCK_SIZE) {
|
||||
u32 size = i+AES_BLOCK_SIZE >= data_size
|
||||
? data_size - i
|
||||
: AES_BLOCK_SIZE;
|
||||
|
||||
params[0].data = (void*)((u32)in_data + i);
|
||||
params[0].len = size;
|
||||
params[1].data = (void*) key;
|
||||
params[1].len = key_size;
|
||||
params[2].data = (void*)((u32)out_data + i);
|
||||
params[2].len = size;
|
||||
params[3].data = iv;
|
||||
params[3].len = iv_size;
|
||||
|
||||
ret = IOS_Ioctlv(__aes_fd, command, 2, 2, params);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
iosFree(__aes_hid, params);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 AES_Init(void)
|
||||
{
|
||||
if (__aes_fd >= 0)
|
||||
return -1;
|
||||
|
||||
__aes_fd = IOS_Open("/dev/aes", 0);
|
||||
if (__aes_fd < 0)
|
||||
return __aes_fd;
|
||||
|
||||
//only create heap if it wasn't created yet.
|
||||
//its never disposed, so only create once.
|
||||
if(__aes_hid < 0)
|
||||
__aes_hid = iosCreateHeap(AES_HEAPSIZE);
|
||||
|
||||
if (__aes_hid < 0) {
|
||||
AES_Close();
|
||||
return __aes_hid;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 AES_Close(void)
|
||||
{
|
||||
if (__aes_fd < 0)
|
||||
return -1;
|
||||
|
||||
IOS_Close(__aes_fd);
|
||||
__aes_fd = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 AES_Encrypt(const void* key, u32 key_size, void* iv, u32 iv_size, const void* in_data, void* out_data, u32 data_size)
|
||||
{
|
||||
return AES_ExecuteCommand(AES_IOCTLV_ENCRYPT, key, key_size, iv, iv_size, in_data, out_data, data_size);
|
||||
}
|
||||
|
||||
s32 AES_Decrypt(const void* key, u32 key_size, void* iv, u32 iv_size, const void* in_data, void* out_data, u32 data_size)
|
||||
{
|
||||
return AES_ExecuteCommand(AES_IOCTLV_DECRYPT, key, key_size, iv, iv_size, in_data, out_data, data_size);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,53 @@
|
|||
/*-------------------------------------------------------------
|
||||
|
||||
aes.h -- AES Engine
|
||||
|
||||
Copyright (C) 2022
|
||||
GaryOderNichts
|
||||
Joris 'DacoTaco' Vermeylen info@dacotaco.com
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you
|
||||
must not claim that you wrote the original software. If you use
|
||||
this software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
-------------------------------------------------------------*/
|
||||
|
||||
|
||||
#ifndef __AES_H___
|
||||
#define __AES_H___
|
||||
|
||||
#if defined(HW_RVL)
|
||||
#include <gctypes.h>
|
||||
#include <gcutil.h>
|
||||
|
||||
#define AES_BLOCK_SIZE 0x10000
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
s32 AES_Init(void);
|
||||
s32 AES_Close(void);
|
||||
s32 AES_Decrypt(const void* key, u32 key_size, void* iv, u32 iv_size, const void* in_data, void* out_data, u32 data_size);
|
||||
s32 AES_Encrypt(const void* key, u32 key_size, void* iv, u32 iv_size, const void* in_data, void* out_data, u32 data_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
#endif
|
|
@ -1,56 +1,32 @@
|
|||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fileops.h"
|
||||
#include "malloc.h"
|
||||
|
||||
static struct stat st;
|
||||
|
||||
bool FSOPFileExists(const char* file)
|
||||
{
|
||||
FILE* f;
|
||||
f = fopen(file, "rb");
|
||||
if (f)
|
||||
{
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return !stat(file, &st) && !S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
bool FSOPFolderExists(const char* path)
|
||||
{
|
||||
DIR* dir;
|
||||
dir = opendir(path);
|
||||
if (dir)
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return !stat(path, &st) && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
size_t FSOPGetFileSizeBytes(const char* path)
|
||||
{
|
||||
FILE* f;
|
||||
size_t size = 0;
|
||||
if (stat(path, &st) < 0) return 0;
|
||||
|
||||
f = fopen(path, "rb");
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = ftell(f);
|
||||
fclose(f);
|
||||
|
||||
return size;
|
||||
return st.st_size;
|
||||
}
|
||||
|
||||
void FSOPDeleteFile(const char* file)
|
||||
{
|
||||
if (!FSOPFileExists(file))
|
||||
return;
|
||||
|
||||
remove(file);
|
||||
if (FSOPFileExists(file))
|
||||
remove(file);
|
||||
}
|
||||
|
||||
void FSOPMakeFolder(const char* path)
|
||||
|
@ -85,13 +61,16 @@ s32 FSOPReadOpenFile(FILE* fp, void* buffer, u32 offset, u32 length)
|
|||
|
||||
s32 FSOPReadOpenFileA(FILE* fp, void** buffer, u32 offset, u32 length)
|
||||
{
|
||||
*buffer = memalign(32, length);
|
||||
*buffer = memalign32(length);
|
||||
if (!*buffer)
|
||||
return -1;
|
||||
|
||||
s32 ret = FSOPReadOpenFile(fp, *buffer, offset, length);
|
||||
if (ret < 0)
|
||||
if (ret <= 0)
|
||||
{
|
||||
free(*buffer);
|
||||
*buffer = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef __FILEOPS_H__
|
||||
#define __FILEOPS_H__
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <ogcsys.h>
|
||||
|
||||
|
@ -14,4 +15,4 @@ void FSOPMakeFolder(const char* path);
|
|||
s32 FSOPReadOpenFile(FILE* fp, void* buffer, u32 offset, u32 length);
|
||||
s32 FSOPReadOpenFileA(FILE* fp, void** buffer, u32 offset, u32 length);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
static inline void *memalign32(size_t size)
|
||||
{
|
||||
return aligned_alloc(0x20, (size + 0x1F) & ~0x1F);
|
||||
}
|
||||
|
||||
static inline void *memalign64(size_t size)
|
||||
{
|
||||
return aligned_alloc(0x40, (size + 0x3F) & ~0x3F);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <ogcsys.h>
|
||||
#include <ogc/pad.h>
|
||||
#include <wiilight.h>
|
||||
#include <wiidrc/wiidrc.h>
|
||||
#include <unistd.h>
|
||||
|
@ -16,7 +17,7 @@
|
|||
#include "video.h"
|
||||
#include "wad.h"
|
||||
#include "wpad.h"
|
||||
#include <ogc/pad.h>
|
||||
#include "wkb.h"
|
||||
#include "globals.h"
|
||||
#include "iospatch.h"
|
||||
#include "appboot.h"
|
||||
|
@ -646,42 +647,29 @@ int Menu_BatchProcessWads(fatFile *files, int fileCount, char *inFilePath, int i
|
|||
|
||||
if (thisFile->installstate < 0)
|
||||
{
|
||||
char str[41];
|
||||
strncpy(str, thisFile->filename, 40); //Only 40 chars to fit the screen
|
||||
str[40]=0;
|
||||
printf(" %.40s: ", thisFile->filename);
|
||||
i++;
|
||||
|
||||
|
||||
switch (thisFile->installstate)
|
||||
{
|
||||
case -106:
|
||||
{
|
||||
printf(" %s Not installed?\n", str);
|
||||
} break;
|
||||
case -996:
|
||||
{
|
||||
printf(" %s Read error\n", str);
|
||||
} break;
|
||||
case -998:
|
||||
{
|
||||
printf(" %s Skipped\n", str);
|
||||
} break;
|
||||
case -999:
|
||||
{
|
||||
printf(" %s BRICK BLOCKED\n", str);
|
||||
} break;
|
||||
case -1036:
|
||||
{
|
||||
printf(" %s Needed IOS missing\n", str);
|
||||
} break;
|
||||
case -4100:
|
||||
{
|
||||
printf(" %s No trucha bug?\n", str);
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
printf(" %s error %d\n", str, thisFile->installstate);
|
||||
} break;
|
||||
case -106: puts("Not installed?"); break;
|
||||
case -996: puts("Read error"); break;
|
||||
case -998: puts("Skipped"); break;
|
||||
case -999: puts("BRICK BLOCKED"); break;
|
||||
case -1010: puts("Wii System memory full!");
|
||||
case -1022: puts("Content hash mismatch"); break;
|
||||
case -1035: puts("Newer version already installed"); break;
|
||||
case -1036: puts("Needed IOS missing!"); break;
|
||||
case -2011: puts("No trucha bug?"); break;
|
||||
/*
|
||||
* from libogc.
|
||||
* This rarely happens unless the WAD had an invalid ticket/tmd size
|
||||
* (certs were not stripped after downloading from NUS maybe?)
|
||||
*/
|
||||
case ES_EINVAL: puts("Invalid WAD?"); break;
|
||||
|
||||
default: printf("error %d\n", thisFile->installstate); break;
|
||||
}
|
||||
|
||||
if(i == 17)
|
||||
|
@ -1084,24 +1072,34 @@ getList:
|
|||
if (--selected < 0)
|
||||
selected = (fileCnt - 1);
|
||||
}
|
||||
else if (buttons & WPAD_BUTTON_LEFT)
|
||||
else if (buttons & WPAD_BUTTON_RIGHT)
|
||||
{
|
||||
selected += ENTRIES_PER_PAGE;
|
||||
|
||||
if (selected >= fileCnt)
|
||||
selected = 0;
|
||||
if (fileCnt - start > ENTRIES_PER_PAGE) {
|
||||
start += ENTRIES_PER_PAGE;
|
||||
selected += ENTRIES_PER_PAGE;
|
||||
if (selected >= fileCnt)
|
||||
selected = fileCnt - 1;
|
||||
}
|
||||
else {
|
||||
selected = fileCnt - 1;
|
||||
}
|
||||
}
|
||||
else if (buttons & WPAD_BUTTON_DOWN)
|
||||
{
|
||||
if (++selected >= fileCnt)
|
||||
selected = 0;
|
||||
}
|
||||
else if (buttons & WPAD_BUTTON_RIGHT)
|
||||
else if (buttons & WPAD_BUTTON_LEFT)
|
||||
{
|
||||
selected -= ENTRIES_PER_PAGE;
|
||||
|
||||
if (selected < 0)
|
||||
selected = (fileCnt - 1);
|
||||
if (start >= ENTRIES_PER_PAGE) {
|
||||
start -= ENTRIES_PER_PAGE;
|
||||
selected -= ENTRIES_PER_PAGE;
|
||||
if (selected < 0)
|
||||
selected = 0;
|
||||
}
|
||||
else {
|
||||
selected = start = 0;
|
||||
}
|
||||
}
|
||||
else if (buttons & WPAD_BUTTON_HOME)
|
||||
{
|
||||
|
@ -1496,9 +1494,10 @@ u32 WaitButtons(void)
|
|||
u32 buttons = 0;
|
||||
u32 buttonsGC = 0;
|
||||
u32 buttonsDRC = 0;
|
||||
u16 buttonsWKB = 0;
|
||||
|
||||
/* Wait for button pressing */
|
||||
while (!buttons && !buttonsGC && !buttonsDRC)
|
||||
while (!(buttons | buttonsGC | buttonsDRC | buttonsWKB))
|
||||
{
|
||||
// Wii buttons
|
||||
buttons = Wpad_GetButtons();
|
||||
|
@ -1509,6 +1508,9 @@ u32 WaitButtons(void)
|
|||
// DRC buttons
|
||||
buttonsDRC = WiiDRC_GetButtons();
|
||||
|
||||
// USB Keyboard buttons
|
||||
buttonsWKB = WKB_GetButtons();
|
||||
|
||||
VIDEO_WaitVSync();
|
||||
}
|
||||
|
||||
|
@ -1581,6 +1583,9 @@ u32 WaitButtons(void)
|
|||
buttons |= WPAD_BUTTON_1;
|
||||
}
|
||||
|
||||
if (buttonsWKB)
|
||||
buttons |= buttonsWKB;
|
||||
|
||||
return buttons;
|
||||
} // WaitButtons
|
||||
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
mini - a Free Software replacement for the Nintendo/BroadOn IOS.
|
||||
SEEPROM support
|
||||
|
||||
Copyright (C) 2008, 2009 Sven Peter <svenpeter@gmail.com>
|
||||
Copyright (C) 2008, 2009 Haxx Enterprises <bushing@gmail.com>
|
||||
Copyright (C) 2008, 2009 Hector Martin "marcan" <marcan@marcansoft.com>
|
||||
Copyright (C) 2008, 2009 John Kelley <wiidev@kelley.ca>
|
||||
Copyright (C) 2020 Pablo Curiel "DarkMatterCore" <pabloacurielz@gmail.com>
|
||||
|
||||
# This code is licensed to you under the terms of the GNU GPL, version 2;
|
||||
# see http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
|
||||
*/
|
||||
|
||||
#include <ogc/machine/processor.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mini_seeprom.h"
|
||||
|
||||
#define HW_REG_BASE 0xd800000
|
||||
#define HW_GPIO1OUT (HW_REG_BASE + 0x0e0)
|
||||
#define HW_GPIO1IN (HW_REG_BASE + 0x0e8)
|
||||
|
||||
#define HW_SEEPROM_BLK_SIZE 2
|
||||
#define HW_SEEPROM_BLK_CNT (SEEPROM_SIZE / HW_SEEPROM_BLK_SIZE)
|
||||
|
||||
#define eeprom_delay() usleep(5)
|
||||
|
||||
enum {
|
||||
GP_EEP_CS = 0x000400,
|
||||
GP_EEP_CLK = 0x000800,
|
||||
GP_EEP_MOSI = 0x001000,
|
||||
GP_EEP_MISO = 0x002000
|
||||
};
|
||||
|
||||
static void seeprom_send_bits(u16 value, u8 bits)
|
||||
{
|
||||
if (!bits || bits > 16) return;
|
||||
|
||||
while(bits--)
|
||||
{
|
||||
if (value & (1 << bits))
|
||||
{
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_MOSI);
|
||||
} else {
|
||||
mask32(HW_GPIO1OUT, GP_EEP_MOSI, 0);
|
||||
}
|
||||
|
||||
eeprom_delay();
|
||||
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CLK);
|
||||
eeprom_delay();
|
||||
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CLK, 0);
|
||||
eeprom_delay();
|
||||
}
|
||||
}
|
||||
|
||||
static u16 seeprom_recv_bits(u8 bits)
|
||||
{
|
||||
if (!bits || bits > 16) return 0;
|
||||
|
||||
int res = 0;
|
||||
|
||||
while(bits--)
|
||||
{
|
||||
res <<= 1;
|
||||
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CLK);
|
||||
eeprom_delay();
|
||||
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CLK, 0);
|
||||
eeprom_delay();
|
||||
|
||||
res |= !!(read32(HW_GPIO1IN) & GP_EEP_MISO);
|
||||
}
|
||||
|
||||
return (u16)res;
|
||||
}
|
||||
|
||||
u16 seeprom_read(void *dst, u16 offset, u16 size)
|
||||
{
|
||||
/*
|
||||
* WiiUBrew told me that you interact with the SEEPROM the exact same way you do on Wii.
|
||||
* However the contents are way different. Like there's absolutely no vWii stuff here.
|
||||
*/
|
||||
if (read16(0xCD8005A0) == 0xCAFE) return 0;
|
||||
|
||||
if (!dst || offset >= SEEPROM_SIZE || !size || (offset + size) > SEEPROM_SIZE) return 0;
|
||||
|
||||
u16 cur_offset = 0;
|
||||
|
||||
u8 *ptr = (u8*)dst;
|
||||
u8 val[HW_SEEPROM_BLK_SIZE] = {0};
|
||||
|
||||
// Calculate block offsets and sizes
|
||||
u8 start_addr = (u8)(offset / HW_SEEPROM_BLK_SIZE);
|
||||
u8 start_addr_offset = (u8)(offset % HW_SEEPROM_BLK_SIZE);
|
||||
|
||||
u8 end_addr = (u8)((offset + size) / HW_SEEPROM_BLK_SIZE);
|
||||
u8 end_addr_size = (u8)((offset + size) % HW_SEEPROM_BLK_SIZE);
|
||||
|
||||
if (!end_addr_size)
|
||||
{
|
||||
end_addr--;
|
||||
end_addr_size = HW_SEEPROM_BLK_SIZE;
|
||||
}
|
||||
|
||||
if (end_addr == start_addr) end_addr_size -= start_addr_offset;
|
||||
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CLK, 0);
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
for(u16 i = start_addr; i <= end_addr; i++)
|
||||
{
|
||||
if (cur_offset >= size) break;
|
||||
|
||||
// Start command cycle
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
|
||||
|
||||
// Send read command + address
|
||||
seeprom_send_bits(0x600 | i, 11);
|
||||
|
||||
// Receive data
|
||||
*((u16*)val) = seeprom_recv_bits(16);
|
||||
|
||||
// End of command cycle
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
// Copy read data to destination buffer
|
||||
if (i == start_addr && start_addr_offset != 0)
|
||||
{
|
||||
// Handle unaligned read at start address
|
||||
memcpy(ptr + cur_offset, val + start_addr_offset, HW_SEEPROM_BLK_SIZE - start_addr_offset);
|
||||
cur_offset += (HW_SEEPROM_BLK_SIZE - start_addr_offset);
|
||||
} else
|
||||
if (i == end_addr && end_addr_size != HW_SEEPROM_BLK_SIZE)
|
||||
{
|
||||
// Handle unaligned read at end address
|
||||
memcpy(ptr + cur_offset, val, end_addr_size);
|
||||
cur_offset += end_addr_size;
|
||||
} else {
|
||||
// Normal read
|
||||
memcpy(ptr + cur_offset, val, HW_SEEPROM_BLK_SIZE);
|
||||
cur_offset += HW_SEEPROM_BLK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
return cur_offset;
|
||||
}
|
||||
|
||||
#if 0
|
||||
u16 seeprom_write(const void *src, u16 offset, u16 size)
|
||||
{
|
||||
if (!src || offset >= SEEPROM_SIZE || !size || (offset + size) > SEEPROM_SIZE) return 0;
|
||||
|
||||
u32 level = 0;
|
||||
u16 cur_offset = 0;
|
||||
|
||||
const u8 *ptr = (const u8*)src;
|
||||
u8 val[HW_SEEPROM_BLK_SIZE] = {0};
|
||||
|
||||
// Calculate block offsets and sizes
|
||||
u8 start_addr = (u8)(offset / HW_SEEPROM_BLK_SIZE);
|
||||
u8 start_addr_offset = (u8)(offset % HW_SEEPROM_BLK_SIZE);
|
||||
|
||||
u8 end_addr = (u8)((offset + size) / HW_SEEPROM_BLK_SIZE);
|
||||
u8 end_addr_size = (u8)((offset + size) % HW_SEEPROM_BLK_SIZE);
|
||||
|
||||
if (!end_addr_size)
|
||||
{
|
||||
end_addr--;
|
||||
end_addr_size = HW_SEEPROM_BLK_SIZE;
|
||||
}
|
||||
|
||||
if (end_addr == start_addr) end_addr_size -= start_addr_offset;
|
||||
|
||||
// Disable CPU interruptions
|
||||
_CPU_ISR_Disable(level);
|
||||
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CLK, 0);
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
// EWEN - Enable programming commands
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
|
||||
seeprom_send_bits(0x4FF, 11);
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
for(u16 i = start_addr; i <= end_addr; i++)
|
||||
{
|
||||
if (cur_offset >= size) break;
|
||||
|
||||
// Copy data to write from source buffer
|
||||
if ((i == start_addr && start_addr_offset != 0) || (i == end_addr && end_addr_size != HW_SEEPROM_BLK_SIZE))
|
||||
{
|
||||
// Read data from SEEPROM to handle unaligned writes
|
||||
|
||||
// Start command cycle
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
|
||||
|
||||
// Send read command + address
|
||||
seeprom_send_bits(0x600 | i, 11);
|
||||
|
||||
// Receive data
|
||||
*((u16*)val) = seeprom_recv_bits(16);
|
||||
|
||||
// End of command cycle
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
if (i == start_addr && start_addr_offset != 0)
|
||||
{
|
||||
// Handle unaligned write at start address
|
||||
memcpy(val + start_addr_offset, ptr + cur_offset, HW_SEEPROM_BLK_SIZE - start_addr_offset);
|
||||
cur_offset += (HW_SEEPROM_BLK_SIZE - start_addr_offset);
|
||||
} else {
|
||||
// Handle unaligned write at end address
|
||||
memcpy(val, ptr + cur_offset, end_addr_size);
|
||||
cur_offset += end_addr_size;
|
||||
}
|
||||
} else {
|
||||
// Normal write
|
||||
memcpy(val, ptr + cur_offset, HW_SEEPROM_BLK_SIZE);
|
||||
cur_offset += HW_SEEPROM_BLK_SIZE;
|
||||
}
|
||||
|
||||
// Start command cycle
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
|
||||
|
||||
// Send write command + address
|
||||
seeprom_send_bits(0x500 | i, 11);
|
||||
|
||||
// Send data
|
||||
seeprom_send_bits(*((u16*)val), 16);
|
||||
|
||||
// End of command cycle
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
// Wait until SEEPROM is ready (write cycle is self-timed so no clocking needed)
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
|
||||
|
||||
do {
|
||||
eeprom_delay();
|
||||
} while(!(read32(HW_GPIO1IN) & GP_EEP_MISO));
|
||||
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
}
|
||||
|
||||
// EWDS - Disable programming commands
|
||||
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
|
||||
seeprom_send_bits(0x400, 11);
|
||||
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
|
||||
eeprom_delay();
|
||||
|
||||
// Enable CPU interruptions
|
||||
_CPU_ISR_Restore(level);
|
||||
|
||||
return cur_offset;
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
mini - a Free Software replacement for the Nintendo/BroadOn IOS.
|
||||
SEEPROM support
|
||||
|
||||
Copyright (C) 2008, 2009 Sven Peter <svenpeter@gmail.com>
|
||||
|
||||
# This code is licensed to you under the terms of the GNU GPL, version 2;
|
||||
# see http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
|
||||
*/
|
||||
|
||||
#ifndef __MINI_SEEPROM_H__
|
||||
#define __MINI_SEEPROM_H__
|
||||
|
||||
#define SEEPROM_SIZE 0x100
|
||||
|
||||
typedef struct
|
||||
{
|
||||
union {
|
||||
struct {
|
||||
u8 boot2version;
|
||||
u8 unknown1;
|
||||
u8 unknown2;
|
||||
u8 pad;
|
||||
u32 update_tag;
|
||||
};
|
||||
u8 data[8];
|
||||
};
|
||||
u16 checksum; // sum of data[] elements?
|
||||
} __attribute__((packed)) eep_boot2_ctr_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
union {
|
||||
u32 nand_gen; // matches offset 0x8 in nand SFFS blocks
|
||||
u8 data[4];
|
||||
};
|
||||
u16 checksum; // sum of data[] elements?
|
||||
} __attribute__((packed)) eep_nand_ctr_t;
|
||||
|
||||
struct SEEPROM
|
||||
{
|
||||
u32 ms_id; // 0x00000002
|
||||
u32 ca_id; // 0x00000001
|
||||
u32 ng_key_id;
|
||||
u8 ng_sig[60];
|
||||
eep_boot2_ctr_t boot2_counters[2];
|
||||
eep_nand_ctr_t nand_counters[3]; // current slot rotates on each write
|
||||
u8 pad0[6];
|
||||
u8 korean_key[16];
|
||||
u8 pad1[116];
|
||||
u16 prng_seed[2]; // u32 with lo word stored first, incremented every time IOS starts. Used with the PRNG key to setup IOS's PRNG (syscalls 73/74 etc.)
|
||||
u8 pad2[4];
|
||||
};
|
||||
_Static_assert(sizeof(struct SEEPROM) == SEEPROM_SIZE, "SEEPROM struct size incorrect!");
|
||||
|
||||
u16 seeprom_read(void *dst, u16 offset, u16 size);
|
||||
// u16 seeprom_write(const void *src, u16 offset, u16 size);
|
||||
|
||||
#endif /* __MINI_SEEPROM_H__ */
|
|
@ -1,10 +1,10 @@
|
|||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <ogcsys.h>
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nand.h"
|
||||
#include "malloc.h"
|
||||
#include "fileops.h"
|
||||
|
||||
/* Buffer */
|
||||
|
@ -118,7 +118,7 @@ u8* NANDReadFromFile(const char* path, u32 offset, u32 length, u32* size)
|
|||
if (!length)
|
||||
length = IOS_Seek(fd, 0, SEEK_END);
|
||||
|
||||
u8* data = (u8*)memalign(0x40, length);
|
||||
u8* data = memalign64(length);
|
||||
if (!data)
|
||||
{
|
||||
*size = 0;
|
||||
|
@ -155,9 +155,10 @@ u8* NANDLoadFile(const char* path, u32* size)
|
|||
|
||||
s32 NANDWriteFileSafe(const char* path, u8* data, u32 size)
|
||||
{
|
||||
char tmpPath[ISFS_MAXPATH] ATTRIBUTE_ALIGN(64);
|
||||
|
||||
NANDInitialize();
|
||||
|
||||
char* tmpPath = (char*)memalign(0x40, ISFS_MAXPATH);
|
||||
|
||||
u32 i;
|
||||
|
||||
for (i = strlen(path); i > 0; --i)
|
||||
|
@ -175,43 +176,29 @@ s32 NANDWriteFileSafe(const char* path, u8* data, u32 size)
|
|||
ret = ISFS_CreateFile(tmpPath, 0, 3, 3, 3);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
free(tmpPath);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (ret < 0)
|
||||
{
|
||||
if (ret < 0)
|
||||
{
|
||||
free(tmpPath);
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 fd = IOS_Open(tmpPath, 2);
|
||||
if (fd < 0)
|
||||
{
|
||||
free(tmpPath);
|
||||
return fd;
|
||||
}
|
||||
|
||||
ret = IOS_Write(fd, data, size);
|
||||
|
||||
|
||||
IOS_Close(fd);
|
||||
if (ret != size)
|
||||
{
|
||||
free(tmpPath);
|
||||
return ret - 3;
|
||||
}
|
||||
|
||||
if (strcmp(tmpPath, path))
|
||||
ret = ISFS_Rename(tmpPath, path);
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
free(tmpPath);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
#include <ogc/machine/processor.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "otp.h"
|
||||
|
||||
#define HW_OTP_COMMAND (*(vu32*)0xCD8001EC)
|
||||
#define HW_OTP_DATA (*(vu32*)0xCD8001F0)
|
||||
|
||||
#define HW_OTP_BLK_SIZE 4
|
||||
#define HW_OTP_BLK_CNT (OTP_SIZE / HW_OTP_BLK_SIZE)
|
||||
|
||||
u8 otp_read(void *dst, u8 offset, u8 size)
|
||||
{
|
||||
if (!dst || offset >= OTP_SIZE || !size || (offset + size) > OTP_SIZE) return 0;
|
||||
|
||||
u8 cur_offset = 0;
|
||||
|
||||
u8 *ptr = (u8*)dst;
|
||||
u8 val[HW_OTP_BLK_SIZE] = {0};
|
||||
|
||||
// Calculate block offsets and sizes
|
||||
u8 start_addr = (offset / HW_OTP_BLK_SIZE);
|
||||
u8 start_addr_offset = (offset % HW_OTP_BLK_SIZE);
|
||||
|
||||
u8 end_addr = ((offset + size) / HW_OTP_BLK_SIZE);
|
||||
u8 end_addr_size = ((offset + size) % HW_OTP_BLK_SIZE);
|
||||
|
||||
if (!end_addr_size)
|
||||
{
|
||||
end_addr--;
|
||||
end_addr_size = HW_OTP_BLK_SIZE;
|
||||
}
|
||||
|
||||
if (end_addr == start_addr) end_addr_size -= start_addr_offset;
|
||||
|
||||
for(u8 i = start_addr; i <= end_addr; i++)
|
||||
{
|
||||
if (cur_offset >= size) break;
|
||||
|
||||
// Send command + address
|
||||
HW_OTP_COMMAND = (0x80000000 | i);
|
||||
|
||||
// Receive data
|
||||
*((u32*)val) = HW_OTP_DATA;
|
||||
|
||||
// Copy read data to destination buffer
|
||||
if (i == start_addr && start_addr_offset != 0)
|
||||
{
|
||||
// Handle unaligned read at start address
|
||||
memcpy(ptr + cur_offset, val + start_addr_offset, HW_OTP_BLK_SIZE - start_addr_offset);
|
||||
cur_offset += (HW_OTP_BLK_SIZE - start_addr_offset);
|
||||
} else
|
||||
if (i == end_addr && end_addr_size != HW_OTP_BLK_SIZE)
|
||||
{
|
||||
// Handle unaligned read at end address
|
||||
memcpy(ptr + cur_offset, val, end_addr_size);
|
||||
cur_offset += end_addr_size;
|
||||
} else {
|
||||
// Normal read
|
||||
memcpy(ptr + cur_offset, val, HW_OTP_BLK_SIZE);
|
||||
cur_offset += HW_OTP_BLK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
return cur_offset;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef __OTP_H__
|
||||
#define __OTP_H__
|
||||
|
||||
#define OTP_SIZE 0x80
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 boot1_hash[20];
|
||||
u8 common_key[16];
|
||||
u8 ng_id[4];
|
||||
union { // first two bytes of nand_hmac overlap last two bytes of ng_priv
|
||||
struct {
|
||||
u8 ng_priv[30];
|
||||
u8 _wtf1[18];
|
||||
};
|
||||
struct {
|
||||
u8 _wtf2[28];
|
||||
u8 nand_hmac[20];
|
||||
};
|
||||
};
|
||||
u8 nand_key[16];
|
||||
u8 rng_key[16];
|
||||
u32 unk1;
|
||||
u32 unk2; // 0x00000007
|
||||
} otp_t;
|
||||
|
||||
u8 otp_read(void *dst, u8 offset, u8 size);
|
||||
|
||||
#endif /* __OTP_H__ */
|
284
source/sys.c
284
source/sys.c
|
@ -1,14 +1,18 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <malloc.h>
|
||||
#include <ogcsys.h>
|
||||
#include <ogc/es.h>
|
||||
|
||||
#include "sys.h"
|
||||
#include "aes.h"
|
||||
#include "nand.h"
|
||||
#include "mini_seeprom.h"
|
||||
#include "malloc.h"
|
||||
#include "mload.h"
|
||||
#include "ehcmodule_elf.h"
|
||||
|
||||
/* Constants */
|
||||
#define CERTS_LEN 0x280
|
||||
#define CERTS_LEN 0x280
|
||||
|
||||
/* Variables */
|
||||
static const char certs_fs[] ATTRIBUTE_ALIGN(32) = "/sys/cert.sys";
|
||||
|
@ -29,75 +33,104 @@ void __Sys_PowerCallback(void)
|
|||
Sys_Shutdown();
|
||||
}
|
||||
|
||||
bool isIOSstub(u8 ios_number)
|
||||
{
|
||||
u32 tmd_size;
|
||||
tmd_view *ios_tmd;
|
||||
|
||||
|
||||
if((boot2version >= 5) && ( ios_number == 202 || ios_number == 222 || ios_number == 223 || ios_number == 224)) return true;
|
||||
|
||||
ES_GetTMDViewSize(0x0000000100000000ULL | ios_number, &tmd_size);
|
||||
if (!tmd_size)
|
||||
{
|
||||
//getting size failed. invalid or fake tmd for sure!
|
||||
//gprintf("failed to get tmd for ios %d\n",ios_number);
|
||||
return true;
|
||||
}
|
||||
ios_tmd = (tmd_view *)memalign( 32, (tmd_size+31)&(~31) );
|
||||
if(!ios_tmd)
|
||||
{
|
||||
//gprintf("failed to mem align the TMD struct!\n");
|
||||
return true;
|
||||
}
|
||||
memset(ios_tmd , 0, tmd_size);
|
||||
ES_GetTMDView(0x0000000100000000ULL | ios_number, (u8*)ios_tmd , tmd_size);
|
||||
//gprintf("IOS %d is rev %d(0x%x) with tmd size of %u and %u contents\n",ios_number,ios_tmd->title_version,ios_tmd->title_version,tmd_size,ios_tmd->num_contents);
|
||||
/*Stubs have a few things in common:
|
||||
- title version : it is mostly 65280 , or even better : in hex the last 2 digits are 0.
|
||||
example : IOS 60 rev 6400 = 0x1900 = 00 = stub
|
||||
- exception for IOS21 which is active, the tmd size is 592 bytes (or 140 with the views)
|
||||
- the stub ios' have 1 app of their own (type 0x1) and 2 shared apps (type 0x8001).
|
||||
eventho the 00 check seems to work fine , we'll only use other knowledge as well cause some
|
||||
people/applications install an ios with a stub rev >_> ...*/
|
||||
u8 Version = ios_tmd->title_version;
|
||||
|
||||
if((boot2version >= 5) && (ios_number == 249 || ios_number == 250) && (Version < 18)) return true;
|
||||
if(( ios_number == 202 || ios_number == 222 || ios_number == 223 || ios_number == 224) && (Version < 4)) return true;
|
||||
//version now contains the last 2 bytes. as said above, if this is 00, its a stub
|
||||
if ( Version == 0 )
|
||||
{
|
||||
if ( ( ios_tmd->num_contents == 3) && (ios_tmd->contents[0].type == 1 && ios_tmd->contents[1].type == 0x8001 && ios_tmd->contents[2].type == 0x8001) )
|
||||
{
|
||||
//gprintf("IOS %d is a stub\n",ios_number);
|
||||
free(ios_tmd);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//gprintf("IOS %d is active\n",ios_number);
|
||||
free(ios_tmd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//gprintf("IOS %d is active\n",ios_number);
|
||||
free(ios_tmd);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tmdIsStubIOS(tmd* p_tmd)
|
||||
{
|
||||
return
|
||||
p_tmd->sys_version >> 32 == 0
|
||||
&& p_tmd->num_contents == 3
|
||||
&& p_tmd->contents[0].type == 0x0001
|
||||
&& p_tmd->contents[1].type == 0x8001
|
||||
&& p_tmd->contents[2].type == 0x8001;
|
||||
}
|
||||
|
||||
bool ES_CheckHasKoreanKey(void)
|
||||
{
|
||||
aeskey korean_key;
|
||||
unsigned char iv[16] = {};
|
||||
|
||||
__attribute__ ((__aligned__(0x10)))
|
||||
unsigned char data[16] = {0x56, 0x52, 0x6f, 0x63, 0xa1, 0x2c, 0xd1, 0x32, 0x07, 0x99, 0x82, 0x3b, 0x1b, 0x08, 0x17, 0xd0};
|
||||
|
||||
if (seeprom_read(korean_key, offsetof(struct SEEPROM, korean_key), sizeof(korean_key)) != sizeof(korean_key))
|
||||
return false;
|
||||
|
||||
AES_Decrypt(korean_key, 0x10, iv, 0x10, data, data, sizeof(data));
|
||||
|
||||
// return (!strcmp((char*) data, "thepikachugamer")) Just remembered that this is how the Trucha bug came to be
|
||||
return (!memcmp(data, "thepikachugamer", sizeof(data)));
|
||||
}
|
||||
|
||||
bool isIOSstub(u8 ios_number)
|
||||
{
|
||||
u32 tmd_size = 0;
|
||||
tmd_view *ios_tmd;
|
||||
|
||||
if ((boot2version >= 5) && (ios_number == 202 || ios_number == 222 || ios_number == 223 || ios_number == 224))
|
||||
return true;
|
||||
|
||||
ES_GetTMDViewSize(0x0000000100000000ULL | ios_number, &tmd_size);
|
||||
if (!tmd_size)
|
||||
{
|
||||
// getting size failed. invalid or fake tmd for sure!
|
||||
// gprintf("failed to get tmd for ios %d\n",ios_number);
|
||||
return true;
|
||||
}
|
||||
ios_tmd = memalign32(tmd_size);
|
||||
if (!ios_tmd)
|
||||
{
|
||||
// gprintf("failed to mem align the TMD struct!\n");
|
||||
return true;
|
||||
}
|
||||
memset(ios_tmd, 0, tmd_size);
|
||||
ES_GetTMDView(0x0000000100000000ULL | ios_number, (u8 *)ios_tmd, tmd_size);
|
||||
// gprintf("IOS %d is rev %d(0x%x) with tmd size of %u and %u contents\n",ios_number,ios_tmd->title_version,ios_tmd->title_version,tmd_size,ios_tmd->num_contents);
|
||||
/*Stubs have a few things in common:
|
||||
- title version : it is mostly 65280 , or even better : in hex the last 2 digits are 0.
|
||||
example : IOS 60 rev 6400 = 0x1900 = 00 = stub
|
||||
- exception for IOS21 which is active, the tmd size is 592 bytes (or 140 with the views)
|
||||
- the stub ios' have 1 app of their own (type 0x1) and 2 shared apps (type 0x8001).
|
||||
eventho the 00 check seems to work fine , we'll only use other knowledge as well cause some
|
||||
people/applications install an ios with a stub rev >_> ...*/
|
||||
u8 Version = ios_tmd->title_version;
|
||||
|
||||
if ((boot2version >= 5) && (ios_number == 249 || ios_number == 250) && (Version < 18))
|
||||
return true;
|
||||
if ((ios_number == 202 || ios_number == 222 || ios_number == 223 || ios_number == 224) && (Version < 4))
|
||||
return true;
|
||||
// version now contains the last 2 bytes. as said above, if this is 00, its a stub
|
||||
if (Version == 0)
|
||||
{
|
||||
if ((ios_tmd->num_contents == 3) && (ios_tmd->contents[0].type == 1 && ios_tmd->contents[1].type == 0x8001 && ios_tmd->contents[2].type == 0x8001))
|
||||
{
|
||||
// gprintf("IOS %d is a stub\n",ios_number);
|
||||
free(ios_tmd);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// gprintf("IOS %d is active\n",ios_number);
|
||||
free(ios_tmd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// gprintf("IOS %d is active\n",ios_number);
|
||||
free(ios_tmd);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool loadIOS(int ios)
|
||||
{
|
||||
if(isIOSstub(ios)) return false;
|
||||
if (isIOSstub(ios))
|
||||
return false;
|
||||
mload_close();
|
||||
if(IOS_ReloadIOS(ios)>=0)
|
||||
if (IOS_ReloadIOS(ios) >= 0)
|
||||
{
|
||||
if (IOS_GetVersion() != 249 && IOS_GetVersion() != 250)
|
||||
{
|
||||
if (mload_init() >= 0)
|
||||
{
|
||||
data_elf my_data_elf;
|
||||
mload_elf((void *) ehcmodule_elf, &my_data_elf);
|
||||
mload_elf((void *)ehcmodule_elf, &my_data_elf);
|
||||
mload_run_thread(my_data_elf.start, my_data_elf.stack, my_data_elf.size_stack, 0x47);
|
||||
}
|
||||
}
|
||||
|
@ -125,17 +158,20 @@ void Sys_Reboot(void)
|
|||
void Sys_Shutdown(void)
|
||||
{
|
||||
/* Poweroff console */
|
||||
if(CONF_GetShutdownMode() == CONF_SHUTDOWN_IDLE) {
|
||||
if (CONF_GetShutdownMode() == CONF_SHUTDOWN_IDLE)
|
||||
{
|
||||
s32 ret;
|
||||
|
||||
/* Set LED mode */
|
||||
ret = CONF_GetIdleLedMode();
|
||||
if(ret >= 0 && ret <= 2)
|
||||
if (ret >= 0 && ret <= 2)
|
||||
STM_SetLedMode(ret);
|
||||
|
||||
/* Shutdown to idle */
|
||||
STM_ShutdownToIdle();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Shutdown to standby */
|
||||
STM_ShutdownToStandby();
|
||||
}
|
||||
|
@ -143,26 +179,33 @@ void Sys_Shutdown(void)
|
|||
|
||||
void Sys_LoadMenu(void)
|
||||
{
|
||||
/* SYSCALL(exit) does all of this already */
|
||||
exit(0);
|
||||
|
||||
/* Also the code gcc generated for this looks really painful */
|
||||
#if 0
|
||||
int HBC = 0;
|
||||
char * sig = (char *)0x80001804;
|
||||
if( sig[0] == 'S' &&
|
||||
sig[1] == 'T' &&
|
||||
sig[2] == 'U' &&
|
||||
sig[3] == 'B' &&
|
||||
sig[4] == 'H' &&
|
||||
sig[5] == 'A' &&
|
||||
sig[6] == 'X' &&
|
||||
sig[7] == 'X')
|
||||
char *sig = (char *)0x80001804;
|
||||
if (sig[0] == 'S' &&
|
||||
sig[1] == 'T' &&
|
||||
sig[2] == 'U' &&
|
||||
sig[3] == 'B' &&
|
||||
sig[4] == 'H' &&
|
||||
sig[5] == 'A' &&
|
||||
sig[6] == 'X' &&
|
||||
sig[7] == 'X')
|
||||
{
|
||||
HBC=1; // Exit to HBC
|
||||
HBC = 1; // Exit to HBC
|
||||
}
|
||||
|
||||
/* Homebrew Channel stub */
|
||||
if (HBC == 1) {
|
||||
if (HBC == 1)
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
/* Return to the Wii system menu */
|
||||
SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
s32 Sys_GetCerts(signed_blob **certs, u32 *len)
|
||||
|
@ -183,14 +226,101 @@ s32 Sys_GetCerts(signed_blob **certs, u32 *len)
|
|||
IOS_Close(fd);
|
||||
|
||||
/* Set values */
|
||||
if (ret > 0) {
|
||||
if (ret > 0)
|
||||
{
|
||||
*certs = certificates;
|
||||
*len = sizeof(certificates);
|
||||
*len = sizeof(certificates);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 Sys_GetSharedContents(SharedContent** out, u32* count)
|
||||
{
|
||||
if (!out || !count) return false;
|
||||
|
||||
int ret = 0;
|
||||
u32 size;
|
||||
SharedContent* buf = (SharedContent*)NANDLoadFile("/shared1/content.map", &size);
|
||||
|
||||
if (!buf)
|
||||
return (s32)size;
|
||||
|
||||
else if (size % sizeof(SharedContent) != 0) {
|
||||
free(buf);
|
||||
return -996;
|
||||
}
|
||||
|
||||
*out = buf;
|
||||
*count = size / sizeof(SharedContent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Sys_SharedContentPresent(tmd_content* content, SharedContent shared[], u32 count)
|
||||
{
|
||||
if (!shared || !content || !count)
|
||||
return false;
|
||||
|
||||
if (!(content->type & 0x8000))
|
||||
return false;
|
||||
|
||||
for (SharedContent* s_content = shared; s_content < shared + count; s_content++)
|
||||
{
|
||||
if (memcmp(s_content->hash, content->hash, sizeof(sha1)) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Sys_GetcIOSInfo(int IOS, cIOSInfo* out)
|
||||
{
|
||||
int ret;
|
||||
u64 titleID = 0x0000000100000000ULL | IOS;
|
||||
ATTRIBUTE_ALIGN(0x20) char path[ISFS_MAXPATH];
|
||||
u32 size;
|
||||
cIOSInfo* buf = NULL;
|
||||
|
||||
u32 view_size = 0;
|
||||
if (ES_GetTMDViewSize(titleID, &view_size) < 0)
|
||||
return false;
|
||||
|
||||
tmd_view* view = memalign32(view_size);
|
||||
if (!view)
|
||||
return false;
|
||||
|
||||
if (ES_GetTMDView(titleID, (u8*)view, view_size) < 0)
|
||||
goto fail;
|
||||
|
||||
tmd_view_content* content0 = NULL;
|
||||
|
||||
for (tmd_view_content* con = view->contents; con < view->contents + view->num_contents; con++)
|
||||
{
|
||||
if (con->index == 0)
|
||||
content0 = con;
|
||||
}
|
||||
|
||||
if (!content0)
|
||||
goto fail;
|
||||
|
||||
sprintf(path, "/title/00000001/%08x/content/%08x.app", IOS, content0->cid);
|
||||
buf = (cIOSInfo*)NANDLoadFile(path, &size);
|
||||
|
||||
if (!buf || size != 0x40 || buf->hdr_magic != CIOS_INFO_MAGIC || buf->hdr_version != CIOS_INFO_VERSION)
|
||||
goto fail;
|
||||
|
||||
*out = *buf;
|
||||
free(view);
|
||||
free(buf);
|
||||
return true;
|
||||
|
||||
fail:
|
||||
free(view);
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetPRButtons(bool enabled)
|
||||
{
|
||||
gDisablePRButtons = !enabled;
|
||||
|
|
36
source/sys.h
36
source/sys.h
|
@ -1,15 +1,51 @@
|
|||
#ifndef _SYS_H_
|
||||
#define _SYS_H_
|
||||
|
||||
/* /shared1/content.map entry */
|
||||
typedef struct
|
||||
{
|
||||
char filename[8];
|
||||
sha1 hash;
|
||||
} ATTRIBUTE_PACKED SharedContent;
|
||||
|
||||
/* "cIOS build tag" */
|
||||
enum
|
||||
{
|
||||
CIOS_INFO_MAGIC = 0x1EE7C105,
|
||||
CIOS_INFO_VERSION = 1
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 hdr_magic; // 0x1EE7C105
|
||||
u32 hdr_version; // 1
|
||||
u32 cios_version; // Eg. 11
|
||||
u32 ios_base; // Eg. 60
|
||||
|
||||
char name[16];
|
||||
char cios_version_str[16];
|
||||
|
||||
char _padding[16];
|
||||
} cIOSInfo;
|
||||
// _Static_assert(sizeof(cIOSInfo) == 0x40, "Incorrect cIOSInfo struct size, do i really need to pack this..?");
|
||||
|
||||
#define IS_WIIU (*(vu16*)0xCD8005A0 == 0xCAFE)
|
||||
|
||||
extern u32 boot2version;
|
||||
|
||||
/* Prototypes */
|
||||
bool isIOSstub(u8 ios_number);
|
||||
bool tmdIsStubIOS(tmd*);
|
||||
bool loadIOS(int ios);
|
||||
bool ES_CheckHasKoreanKey(void);
|
||||
void Sys_Init(void);
|
||||
void Sys_Reboot(void);
|
||||
void Sys_Shutdown(void);
|
||||
void Sys_LoadMenu(void);
|
||||
s32 Sys_GetCerts(signed_blob **, u32 *);
|
||||
bool Sys_GetcIOSInfo(int IOS, cIOSInfo*);
|
||||
s32 Sys_GetSharedContents(SharedContent** out, u32* count);
|
||||
bool Sys_SharedContentPresent(tmd_content* content, SharedContent shared[], u32 count);
|
||||
void SetPRButtons(bool enabled);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <malloc.h>
|
||||
#include <ogcsys.h>
|
||||
#include <ogc/es.h>
|
||||
|
||||
#include "sha1.h"
|
||||
#include "aes.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "otp.h"
|
||||
#include "malloc.h"
|
||||
|
||||
s32 Title_ZeroSignature(signed_blob *p_sig)
|
||||
{
|
||||
|
@ -79,7 +81,7 @@ s32 Title_GetList(u64 **outbuf, u32 *outlen)
|
|||
{
|
||||
u64 *titles = NULL;
|
||||
|
||||
u32 len, nb_titles;
|
||||
u32 nb_titles;
|
||||
s32 ret;
|
||||
|
||||
/* Get number of titles */
|
||||
|
@ -87,11 +89,9 @@ s32 Title_GetList(u64 **outbuf, u32 *outlen)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Calculate buffer lenght */
|
||||
len = round_up(sizeof(u64) * nb_titles, 32);
|
||||
|
||||
/* Allocate memory */
|
||||
titles = memalign(32, len);
|
||||
titles = memalign32(nb_titles * sizeof(u64));
|
||||
if (!titles)
|
||||
return -1;
|
||||
|
||||
|
@ -126,7 +126,7 @@ s32 Title_GetTicketViews(u64 tid, tikview **outbuf, u32 *outlen)
|
|||
return ret;
|
||||
|
||||
/* Allocate memory */
|
||||
views = (tikview *)memalign(32, sizeof(tikview) * nb_views);
|
||||
views = memalign32(sizeof(tikview) * nb_views);
|
||||
if (!views)
|
||||
return -1;
|
||||
|
||||
|
@ -161,7 +161,7 @@ s32 Title_GetTMD(u64 tid, signed_blob **outbuf, u32 *outlen)
|
|||
return ret;
|
||||
|
||||
/* Allocate memory */
|
||||
p_tmd = memalign(32, round_up(len, 32));
|
||||
p_tmd = memalign32(len);
|
||||
if (!p_tmd)
|
||||
return -1;
|
||||
|
||||
|
@ -290,7 +290,7 @@ s32 Title_GetIOSVersions(u8 **outbuf, u32 *outlen)
|
|||
}
|
||||
|
||||
/* Allocate memory */
|
||||
buffer = (u8 *)memalign(32, cnt);
|
||||
buffer = memalign32(cnt);
|
||||
if (!buffer) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
|
@ -318,3 +318,26 @@ out:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
__attribute__((aligned(0x10)))
|
||||
aeskey WiiCommonKey, vWiiCommonKey;
|
||||
|
||||
void Title_SetupCommonKeys(void)
|
||||
{
|
||||
static bool keys_ok = false;
|
||||
if (keys_ok)
|
||||
return;
|
||||
|
||||
// Grab the Wii common key...
|
||||
otp_read(WiiCommonKey, offsetof(otp_t, common_key), sizeof(aeskey));
|
||||
|
||||
// ...and decrypt the vWii common key with it.
|
||||
static const unsigned char vwii_key_enc_bin[0x10] = { 0x6e, 0x18, 0xdb, 0x23, 0x84, 0x7c, 0xba, 0x6c, 0x19, 0x31, 0xa4, 0x17, 0x9b, 0xaf, 0x8e, 0x09 };
|
||||
unsigned char iv[0x10] = {};
|
||||
|
||||
memcpy(vWiiCommonKey, vwii_key_enc_bin, sizeof(vwii_key_enc_bin));
|
||||
AES_Decrypt(WiiCommonKey, sizeof(aeskey), iv, sizeof(iv), vWiiCommonKey, vWiiCommonKey, sizeof(aeskey));
|
||||
|
||||
keys_ok = true;
|
||||
return;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
#ifndef _TITLE_H_
|
||||
#define _TITLE_H_
|
||||
|
||||
#include <ogc/es.h>
|
||||
|
||||
/* Constants */
|
||||
#define BLOCK_SIZE 1024
|
||||
#define BLOCK_SIZE 0x4000
|
||||
|
||||
/* Variables */
|
||||
extern aeskey WiiCommonKey, vWiiCommonKey;
|
||||
|
||||
/* Prototypes */
|
||||
s32 Title_ZeroSignature(signed_blob *);
|
||||
|
@ -15,5 +20,6 @@ s32 Title_GetVersion(u64, u16 *);
|
|||
s32 Title_GetSysVersion(u64, u64 *);
|
||||
s32 Title_GetSize(u64, u32 *);
|
||||
s32 Title_GetIOSVersions(u8 **, u32 *);
|
||||
void Title_SetupCommonKeys(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,12 +8,15 @@
|
|||
#include <wiidrc/wiidrc.h>
|
||||
|
||||
#include "sys.h"
|
||||
#include "title.h"
|
||||
#include "aes.h"
|
||||
#include "gui.h"
|
||||
#include "menu.h"
|
||||
#include "restart.h"
|
||||
#include "sys.h"
|
||||
#include "video.h"
|
||||
#include "wpad.h"
|
||||
#include "wkb.h"
|
||||
#include "fat.h"
|
||||
#include "nand.h"
|
||||
#include "globals.h"
|
||||
|
@ -179,8 +182,12 @@ int main(int argc, char **argv)
|
|||
Wpad_Init();
|
||||
PAD_Init();
|
||||
WiiDRC_Init();
|
||||
WKB_Initialize();
|
||||
WIILIGHT_Init();
|
||||
|
||||
AES_Init();
|
||||
Title_SetupCommonKeys();
|
||||
|
||||
/* Print disclaimer */
|
||||
//Disclaimer();
|
||||
|
||||
|
|
847
source/wad.c
847
source/wad.c
File diff suppressed because it is too large
Load Diff
161
source/wkb.c
161
source/wkb.c
|
@ -1,47 +1,150 @@
|
|||
/*
|
||||
* Most of this code was adapted from Priiloader.
|
||||
* https://github.com/DacoTaco/priiloader/blob/master/tools/DacosLove/source/Input.cpp
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ogc/lwp.h>
|
||||
#include <wiiuse/wpad.h>
|
||||
|
||||
#include "wkb.h"
|
||||
|
||||
/*
|
||||
|
||||
s32 USBKeyboard_Open(const eventcallback cb);
|
||||
void USBKeyboard_Close(void);
|
||||
|
||||
bool USBKeyboard_IsConnected(void);
|
||||
s32 USBKeyboard_Scan(void);
|
||||
|
||||
s32 USBKeyboard_SetLed(const USBKeyboard_led led, bool on);
|
||||
s32 USBKeyboard_ToggleLed(const USBKeyboard_led led);
|
||||
*/
|
||||
|
||||
s32 WkbInit(void)
|
||||
enum
|
||||
{
|
||||
s32 retval = 0;
|
||||
/* Configuration i guess */
|
||||
WKB_THREAD_PRIORITY = 0x7F,
|
||||
WKB_THREAD_STACK = 0x4000,
|
||||
WKB_THREAD_UDELAY = 400,
|
||||
WKB_ANIMATION_UDELAY= 250000,
|
||||
|
||||
retval = USBKeyboard_Initialize();
|
||||
/* The keys themselves */
|
||||
WKB_ARROW_UP = 0x52,
|
||||
WKB_ARROW_DOWN = 0x51,
|
||||
WKB_ARROW_LEFT = 0x50,
|
||||
WKB_ARROW_RIGHT = 0x4F,
|
||||
|
||||
return (retval);
|
||||
WKB_SPACEBAR = 0x2C,
|
||||
WKB_ENTER = 0x28,
|
||||
WKB_NUMPAD_ENTER = 0x58,
|
||||
WKB_BACKSPACE = 0x2A,
|
||||
WKB_DELETE = 0x4C,
|
||||
WKB_ESCAPE = 0x29,
|
||||
WKB_HOME = 0x4A,
|
||||
|
||||
} // WkbInit
|
||||
WKB_KEY_PLUS = 0x2E,
|
||||
WKB_KEY_MINUS = 0x2D,
|
||||
|
||||
s32 WkbDeInit(void)
|
||||
WKB_KEY_X = 0x1B,
|
||||
WKB_KEY_Y = 0x1C,
|
||||
WKB_KEY_1 = 0x1E,
|
||||
WKB_KEY_2 = 0x1F,
|
||||
};
|
||||
|
||||
static lwp_t WKBThreadHandle = LWP_THREAD_NULL;
|
||||
static volatile bool WKBThreadActive;
|
||||
static u16 WKBButtonsPressed;
|
||||
|
||||
static void WKBEventHandler(USBKeyboard_event evt)
|
||||
{
|
||||
s32 retval = 0;
|
||||
// OSReport("Keyboard event: {%i, 0x%02hhx}", evt.type, evt.keyCode);
|
||||
|
||||
retval = USBKeyboard_Deinitialize();
|
||||
if (!(evt.type == USBKEYBOARD_PRESSED || evt.type == USBKEYBOARD_RELEASED))
|
||||
return;
|
||||
|
||||
return (retval);
|
||||
u16 button;
|
||||
switch (evt.keyCode)
|
||||
{
|
||||
/*
|
||||
* Maybe I should create an array with a map of keycodes to the corresponding WPAD buttons.
|
||||
* Like there's just this u16 WKBKeyMap[0x100] somewhere here and I can just say button = WKBKeyMap[evt.keyCode];
|
||||
*/
|
||||
case WKB_ENTER:
|
||||
case WKB_NUMPAD_ENTER: button = WPAD_BUTTON_A; break;
|
||||
case WKB_BACKSPACE: button = WPAD_BUTTON_B; break;
|
||||
|
||||
} // WkbDeInit
|
||||
case WKB_ARROW_UP: button = WPAD_BUTTON_UP; break;
|
||||
case WKB_ARROW_DOWN: button = WPAD_BUTTON_DOWN; break;
|
||||
case WKB_ARROW_LEFT: button = WPAD_BUTTON_LEFT; break;
|
||||
case WKB_ARROW_RIGHT: button = WPAD_BUTTON_RIGHT; break;
|
||||
|
||||
u32 WkbWaitKey (void)
|
||||
case WKB_ESCAPE:
|
||||
case WKB_HOME: button = WPAD_BUTTON_HOME; break;
|
||||
case WKB_KEY_PLUS: button = WPAD_BUTTON_PLUS; break;
|
||||
case WKB_KEY_MINUS: button = WPAD_BUTTON_MINUS; break;
|
||||
case WKB_KEY_1: button = WPAD_BUTTON_1; break;
|
||||
case WKB_KEY_2: button = WPAD_BUTTON_2; break;
|
||||
|
||||
default: return;
|
||||
}
|
||||
|
||||
if (evt.type == USBKEYBOARD_PRESSED)
|
||||
WKBButtonsPressed |= button;
|
||||
else
|
||||
WKBButtonsPressed &= ~button;
|
||||
}
|
||||
|
||||
static void* WKBThread(__attribute__((unused)) void* arg)
|
||||
{
|
||||
u32 retval = 0;
|
||||
while (WKBThreadActive)
|
||||
{
|
||||
/*
|
||||
* Despite having a return type of s32, USBKeyboard_Open() returns 1 if it was successful, rather than 0.
|
||||
* So this statement right here will check if a USB keyboard was detected, and if not, try open it again.
|
||||
*/
|
||||
if (!USBKeyboard_IsConnected() && USBKeyboard_Open(WKBEventHandler) == true)
|
||||
{
|
||||
/*
|
||||
* And once it does open it successfully:
|
||||
*
|
||||
* "wake up the keyboard by sending it a command.
|
||||
* im looking at you, bastard LINQ keyboard."
|
||||
* - https://github.com/DacoTaco/priiloader/blob/master/tools/DacosLove/source/Input.cpp#L93-L94
|
||||
*
|
||||
* Just thought i would make this a fun lil animation :)
|
||||
*/
|
||||
for (int led = 0; led < 3; led++) { USBKeyboard_SetLed(led, true); usleep(WKB_ANIMATION_UDELAY); }
|
||||
}
|
||||
|
||||
// Stub
|
||||
return (retval);
|
||||
USBKeyboard_Scan();
|
||||
usleep(WKB_THREAD_UDELAY);
|
||||
}
|
||||
|
||||
} // WkbWaitKey
|
||||
// for (int led = 3; led; led--) { USBKeyboard_SetLed(led, false); usleep(WKB_ANIMATION_UDELAY); }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void WKB_Initialize(void)
|
||||
{
|
||||
USB_Initialize();
|
||||
USBKeyboard_Initialize();
|
||||
|
||||
WKBThreadActive = true;
|
||||
LWP_CreateThread(&WKBThreadHandle, WKBThread, NULL, NULL, WKB_THREAD_STACK, WKB_THREAD_PRIORITY);
|
||||
atexit(WKB_Deinitialize);
|
||||
}
|
||||
|
||||
void WKB_Deinitialize(void)
|
||||
{
|
||||
WKBThreadActive = false;
|
||||
usleep(WKB_THREAD_UDELAY);
|
||||
|
||||
USBKeyboard_Close();
|
||||
USBKeyboard_Deinitialize();
|
||||
|
||||
if (WKBThreadHandle != LWP_THREAD_NULL)
|
||||
LWP_JoinThread(WKBThreadHandle, NULL);
|
||||
|
||||
WKBThreadHandle = LWP_THREAD_NULL;
|
||||
}
|
||||
|
||||
u16 WKB_GetButtons(void)
|
||||
{
|
||||
u16 buttons = WKBButtonsPressed;
|
||||
WKBButtonsPressed = 0;
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
//void Wpad_Disconnect(void);
|
||||
//u32 Wpad_GetButtons(void);
|
||||
|
||||
|
||||
|
|
|
@ -5,15 +5,14 @@
|
|||
//#include <stdlib.h>
|
||||
//#include <string.h>
|
||||
//#include <malloc.h>
|
||||
#include <ogcsys.h> // u8, u16, etc...
|
||||
#include <gctypes.h> // u8, u16, etc...
|
||||
|
||||
#include <wiikeyboard/keyboard.h>
|
||||
#include <wiikeyboard/usbkeyboard.h>
|
||||
|
||||
/* Prototypes */
|
||||
s32 WkbInit(void);
|
||||
u32 WkbWaitKey (void);
|
||||
//void Wpad_Disconnect(void);
|
||||
//u32 Wpad_GetButtons(void);
|
||||
void WKB_Initialize(void);
|
||||
void WKB_Deinitialize(void);
|
||||
u16 WKB_GetButtons(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<app version="1">
|
||||
<name>YAWM ModMii Edition</name>
|
||||
<version>1.0</version>
|
||||
<version>1.1</version>
|
||||
<coder>Various</coder>
|
||||
<short_description>Manage WADs and Launch Apps</short_description>
|
||||
<long_description>Yet Another Wad Manager ModMii Edition (yawmME)
|
||||
|
|
Loading…
Reference in New Issue