altra64/utils.c

1157 lines
35 KiB
C
Raw Normal View History

2014-06-29 01:10:11 -04:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <libdragon.h>
#include <n64sys.h>
#include "everdrive.h"
#include "sys.h"
#include "types.h"
#include "utils.h"
#include "sram.h"
#define STBI_HEADER_FILE_ONLY
#include "stb_image.c"
extern short int gCheats; /* 0 = off, 1 = select, 2 = all */
extern short int force_tv;
extern short int boot_country;
static u8 __attribute__((aligned(16))) dmaBuf[128*1024];
static volatile struct _PI_regs_s * const _PI_regs = (struct _PI_regs_s *)0xa4600000;
2014-06-30 03:49:02 -04:00
int is_valid_rom(unsigned char *buffer) {
2014-06-29 01:10:11 -04:00
/* Test if rom is a native .z64 image with header 0x80371240. [ABCD] */
if((buffer[0]==0x80)&&(buffer[1]==0x37)&&(buffer[2]==0x12)&&(buffer[3]==0x40))
return 0;
/* Test if rom is a byteswapped .v64 image with header 0x37804012. [BADC] */
else if((buffer[0]==0x37)&&(buffer[1]==0x80)&&(buffer[2]==0x40)&&(buffer[3]==0x12))
return 1;
/* Test if rom is a wordswapped .n64 image with header 0x40123780. [DCBA] */
else if((buffer[0]==0x40)&&(buffer[1]==0x12)&&(buffer[2]==0x37)&&(buffer[3]==0x80))
return 2;
else
return 0;
}
2014-06-30 03:49:02 -04:00
void swap_header(unsigned char* header, int loadlength) {
2014-06-29 01:10:11 -04:00
unsigned char temp;
int i;
/* Btyeswap if .v64 image. */
2014-06-30 03:49:02 -04:00
if( header[0]==0x37) {
for (i = 0; i < loadlength; i+=2) {
2014-06-29 01:10:11 -04:00
temp= header[i];
2014-06-30 03:49:02 -04:00
header[i]= header[i+1];
header[i+1]=temp;
2014-06-29 01:10:11 -04:00
}
}
/* Wordswap if .n64 image. */
2014-06-30 03:49:02 -04:00
else if( header[0]==0x40) {
for (i = 0; i < loadlength; i+=4) {
2014-06-29 01:10:11 -04:00
temp= header[i];
2014-06-30 03:49:02 -04:00
header[i]= header[i+3];
header[i+3]=temp;
2014-06-29 01:10:11 -04:00
temp= header[i+1];
2014-06-30 03:49:02 -04:00
header[i+1]= header[i+2];
header[i+2]=temp;
2014-06-29 01:10:11 -04:00
}
2014-06-30 03:49:02 -04:00
}
2014-06-29 01:10:11 -04:00
}
u8 getCicType(u8 bios_cic) {
u8 cic_buff[2048];
volatile u8 cic_chip;
2014-06-30 03:49:02 -04:00
volatile u32 val;
2014-06-29 01:10:11 -04:00
if (bios_cic) {
evd_setCfgBit(ED_CFG_SDRAM_ON, 0);
sleep(10);
val = *(u32 *) 0xB0000170;
dma_read_s(cic_buff, 0xB0000040, 1024);
cic_chip = get_cic(cic_buff);
evd_setCfgBit(ED_CFG_SDRAM_ON, 1);
sleep(10);
2014-06-30 03:49:02 -04:00
}
else {
2014-06-29 01:10:11 -04:00
val = *(u32 *) 0xB0000170;
dma_read_s(cic_buff, 0xB0000040, 1024);
cic_chip = get_cic(cic_buff);
}
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
return cic_chip;
}
2014-06-30 03:49:02 -04:00
int get_cic(unsigned char *buffer) {
2014-06-29 01:10:11 -04:00
unsigned int crc;
// figure out the CIC
crc = CRC_Calculate(0, buffer, 1000);
2014-06-30 03:49:02 -04:00
switch(crc) {
case 0x303faac9:
case 0xf0da3d50:
return 1;
case 0xf3106101:
return 2;
case 0xe7cd9d51:
return 3;
case 0x7ae65c9:
return 5;
case 0x86015f8f:
return 6;
2014-06-29 01:10:11 -04:00
}
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
return 2;
}
2014-06-30 03:49:02 -04:00
int get_cic_save(char *cartid, int *cic, int *save) {
2014-06-29 01:10:11 -04:00
// variables
int NUM_CARTS = 137;
int i;
//data arrays
2014-06-30 03:49:02 -04:00
/*
char *names[] = {
"gnuboy64lite", "FRAMTestRom", "SRAMTestRom", "Worms Armageddon",
"Super Smash Bros.", "Banjo-Tooie", "Blast Corps", "Bomberman Hero",
"Body Harvest", "Banjo-Kazooie", "Bomberman 64",
"Bomberman 64: Second Attack", "Command & Conquer", "Chopper Attack",
"NBA Courtside 2 featuring Kobe Bryant", "Penny Racers",
"Chameleon Twist", "Cruis'n USA", "Cruis'n World",
"Legend of Zelda: Majora's Mask, The", "Donkey Kong 64",
"Donkey Kong 64", "Donald Duck: Goin' Quackers",
"Loony Toons: Duck Dodgers", "Diddy Kong Racing", "PGA European Tour",
"Star Wars Episode 1 Racer", "AeroFighters Assault", "Bass Hunter 64",
"Conker's Bad Fur Day", "F-1 World Grand Prix", "Star Fox 64",
"F-Zero X", "GT64 Championship Edition", "GoldenEye 007", "Glover",
"Bomberman 64", "Indy Racing 2000",
"Indiana Jones and the Infernal Machine", "Jet Force Gemini",
"Jet Force Gemini", "Earthworm Jim 3D", "Snowboard Kids 2",
"Kirby 64: The Crystal Shards", "Fighters Destiny",
"Major League Baseball featuring Ken Griffey Jr.",
"Killer Instinct Gold", "Ken Griffey Jr's Slugfest", "Mario Kart 64",
"Mario Party", "Lode Runner 3D", "Megaman 64", "Mario Tennis",
"Mario Golf", "Mission: Impossible", "Mickey's Speedway USA",
"Monopoly", "Paper Mario", "Multi-Racing Championship",
"Big Mountain 2000", "Mario Party 3", "Mario Party 2", "Excitebike 64",
"Dr. Mario 64", "Star Wars Episode 1: Battle for Naboo",
"Kobe Bryant in NBA Courtside", "Excitebike 64",
"Ogre Battle 64: Person of Lordly Caliber", "Pokémon Stadium 2",
"Pokémon Stadium 2", "Perfect Dark", "Pokémon Snap",
"Hey you, Pikachu!", "Pokémon Snap", "Pokémon Puzzle League",
"Pokémon Stadium", "Pokémon Stadium", "Pilotwings 64",
"Top Gear Overdrive", "Resident Evil 2", "New Tetris, The",
"Star Wars: Rogue Squadron", "Ridge Racer 64",
"Star Soldier: Vanishing Earth", "AeroFighters Assault",
"Starshot Space Circus", "Super Mario 64", "Starcraft 64",
"Rocket: Robot on Wheels", "Space Station Silicon Valley",
"Star Wars: Shadows of the Empire", "Tigger's Honey Hunt",
"1080º Snowboarding", "Tom & Jerry in Fists of Furry",
"Mischief Makers", "All-Star Tennis '99", "Tetrisphere",
"V-Rally Edition '99", "V-Rally Edition '99", "WCW/NWO Revenge",
"WWF: No Mercy", "Waialae Country Club: True Golf Classics",
"Wave Race 64", "Worms Armageddon", "WWF: Wrestlemania 2000",
"Cruis'n Exotica", "Yoshi's Story", "Harvest Moon 64",
"Legend of Zelda: Ocarina of Time, The",
"Legend of Zelda: Majora's Mask, The", "Airboarder 64",
"Bakuretsu Muteki Bangaioh", "Choro-Q 64 II", "Custom Robo",
"Custom Robo V2", "Densha de Go! 64", "Doraemon: Mittsu no Seireiseki",
"Dezaemon 3D", "Transformers Beast Wars",
"Transformers Beast Wars Metals", "64 Trump Collection", "Bass Rush",
"ECW Hardcore Revolution", "40 Winks", "Aero Gauge",
"Aidyn Chronicles The First Mage", "Derby Stallion 64",
"Doraemon 2 - Hikari no Shinden", "Doraemon 3 - Nobi Dai No Machi SOS",
"F-1 World Grand Prix II", "Fushigi no Dungeon - Furai no Shiren 2",
"Heiwa Pachinko World 64", "Neon Genesis Evangelion",
"Racing Simulation", "Tsumi to Batsu", "Sonic Wings Assault",
"Virtual Pro Wrestling", "Virtual Pro Wrestling 2", "Wild Choppers"
};
*/
char *cartIDs[] = {
"DZ", "B6", "ZY", "ZZ", "AD", "AL", "B7", "BC", "BD", "BH", "BK", "BM",
"BV", "CC", "CH", "CK", "CR", "CT", "CU", "CW", "DL", "DO", "DP", "DQ",
"DU", "DY", "EA", "EP", "ER", "FH", "FU", "FW", "FX", "FZ", "GC", "GE",
"GV", "HA", "IC", "IJ", "JD", "JF", "JM", "K2", "K4", "KA", "KG", "KI",
"KJ", "KT", "LB", "LR", "M6", "M8", "MF", "MI", "ML", "MO", "MQ", "MR",
"MU", "MV", "MW", "MX", "N6", "NA", "NB", "NX", "OB", "P2", "P3", "PD",
"PF", "PG", "PH", "PN", "PO", "PS", "PW", "RC", "RE", "RI", "RS", "RZ",
"S6", "SA", "SC", "SM", "SQ", "SU", "SV", "SW", "T9", "TE", "TJ", "TM",
"TN", "TP", "VL", "VY", "W2", "W4", "WL", "WR", "WU", "WX", "XO", "YS",
"YW", "ZL", "ZS", "AB", "BN", "CG", "CX", "CZ", "D6", "DR", "DZ", "OH",
"TB", "TC", "VB", "WI", "4W", "AG", "AY", "DA", "D2", "3D", "F2", "SI",
"HP", "EV", "MG", "GU", "SA", "VP", "A2", "WC"
};
/*
int saveTypes[] = {
5, 1, 6, 5, 5, 5, 5, 5, 5, 4, 5, 4, 5, 5, 5, 6, 4, 6, 6, 5, 5, 5, 5, 6,
5, 5, 6, 5, 5, 1, 5, 5, 5, 5, 5, 5, 4, 4, 5, 5, 5, 5, 1, 5, 4, 5, 5, 5,
4, 6, 1, 5, 5, 5, 4, 5, 5, 6, 5, 6, 5, 5, 6, 6, 1, 4, 4, 6, 4, 5, 4, 4,
4, 4, 5, 5, 1, 1, 5, 6, 5, 5, 5, 5, 4, 5, 5, 5, 4, 1, 5, 5, 5, 5, 5, 5,
1, 4, 5, 5, 5, 1, 5, 6, 1, 1, 4, 5, 5, 5, 5, 6, 1, 5, 1, 5, 5, 5, 1, 1,
5, 5, 1, 1, 6, 6, 6, 4, 5, 6, 5, 5, 5, 1, 1, 5
};
*/
// Banjo-Tooie B7 -> set to sram 'cause crk converts ek16->sram
int saveTypes[] = {
2, 1, 5, 1, 3, 1, 1, 3, 3, 3, 3, 3, 3, 5, 3, 5, 3, 3, 3, 4, 5, 4, 4, 3,
3, 3, 3, 4, 3, 3, 4, 3, 3, 1, 3, 3, 3, 3, 3, 3, 5, 5, 3, 3, 3, 3, 1, 3,
5, 3, 3, 3, 5, 4, 1, 3, 3, 3, 5, 3, 3, 4, 3, 4, 3, 3, 4, 4, 1, 5, 5, 4,
5, 3, 5, 5, 5, 5, 3, 3, 1, 1, 3, 4, 3, 3, 3, 3, 5, 3, 3, 3, 5, 1, 3, 3,
3, 3, 3, 3, 1, 5, 3, 3, 3, 1, 3, 4, 1, 1, 5, 3, 3, 3, 3, 4, 1, 3, 1, 3,
3, 3, 1, 1, 3, 3, 1, 1, 4, 4, 4, 5, 3, 4, 3, 3, 3, 1, 1, 3
};
//bt cic to 2 pos6 was 5
int cicTypes[] = {
2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 6, 5, 5, 5, 2,
2, 3, 2, 2, 2, 2, 5, 2, 1, 6, 2, 2, 2, 2, 2, 2, 5, 5, 2, 2, 3, 2, 3, 2,
3, 2, 2, 2, 2, 2, 2, 2, 5, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 3, 3, 5,
3, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
};
2014-06-29 01:10:11 -04:00
// search for cartid
for (i=0; i<NUM_CARTS; i++)
if (strcmp(cartid, cartIDs[i]) == 0)
break;
2014-06-30 03:49:02 -04:00
if (i == NUM_CARTS) {
2014-06-29 01:10:11 -04:00
// cart not in list
*cic = 2;
*save = 0;
return 0; // not found
}
// cart found
*cic = cicTypes[i];
*save = saveTypes[i];
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
return 1; // found
}
2014-06-30 03:49:02 -04:00
const char* saveTypeToExtension(int type, int etype) {
static char* str = "SAV";
if(etype==0){
switch(type) {
case 0: str = "OFF"; break;
case 1: str = "SRM"; break;
case 2: str = "128"; break;
case 3: str = "E4K"; break;
case 4: str = "E16"; break;
case 5: str = "FLA"; break;
default: str = "SAV";
}
}
else {
switch(type) {
case 0: str = "OFF"; break;
case 1: str = "SRA"; break;
case 2: str = "SRA"; break;
case 3: str = "EEP"; break;
case 4: str = "EEP"; break;
case 5: str = "FLA"; break;
default: str = "SAV";
}
}
return str;
2014-06-29 01:10:11 -04:00
}
2014-06-30 03:49:02 -04:00
int saveTypeToSize(int type) {
switch(type) {
2014-06-29 01:10:11 -04:00
case 0: return 0; break;
case 1: return SAVE_SIZE_SRAM; break;
case 2: return SAVE_SIZE_SRAM128; break;
case 3: return SAVE_SIZE_EEP4k; break;
case 4: return SAVE_SIZE_EEP16k; break;
case 5: return SAVE_SIZE_FLASH; break;
default: return 0;
}
}
/*
#define SAVE_TYPE_OFF 0
#define SAVE_TYPE_SRAM 1
#define SAVE_TYPE_SRAM128 2
#define SAVE_TYPE_EEP4k 3
#define SAVE_TYPE_EEP16k 4
#define SAVE_TYPE_FLASH 5
*/
//switch to the correct dump function
2014-06-30 03:49:02 -04:00
int getSaveFromCart(int stype, uint8_t *buffer) {
int ret=0;
switch(stype) {
case 0: return 0;
2014-06-29 01:10:11 -04:00
case 1: ret = getSRAM32(buffer); break;
case 2: ret = getSRAM128(buffer); break;
case 3: ret = getEeprom4k(buffer); break;
case 4: ret = getEeprom16k(buffer); break;
case 5: ret = getFlashRAM(buffer); break;
default: return 0;
}
2014-06-30 03:49:02 -04:00
return ret;
2014-06-29 01:10:11 -04:00
}
//switch to the correct upload function
int pushSaveToCart(int stype, uint8_t *buffer){
2014-06-30 03:49:02 -04:00
int ret=0;
switch(stype) {
case 0: return 0;
2014-06-29 01:10:11 -04:00
case 1: ret = setSRAM32(buffer); break;
case 2: ret = setSRAM128(buffer); break;
case 3: ret = setEeprom4k(buffer); break;
case 4: ret = setEeprom16k(buffer); break;
case 5: ret = setFlashRAM(buffer); break;
default: return 0;
}
2014-06-30 03:49:02 -04:00
return ret;
2014-06-29 01:10:11 -04:00
}
2014-06-30 03:49:02 -04:00
int getSRAM( uint8_t *buffer, int size){
while (dma_busy()) ;
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
IO_WRITE(PI_BSD_DOM2_LAT_REG, 0x05);
2014-06-29 01:10:11 -04:00
IO_WRITE(PI_BSD_DOM2_PWD_REG, 0x0C);
IO_WRITE(PI_BSD_DOM2_PGS_REG, 0x0D);
IO_WRITE(PI_BSD_DOM2_RLS_REG, 0x02);
2014-06-30 03:49:02 -04:00
while (dma_busy()) ;
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
PI_Init();
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
sleep(1000);
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
while (dma_busy()) ;
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
PI_DMAFromSRAM(buffer, 0, size) ;
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
while (dma_busy()) ;
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
IO_WRITE(PI_BSD_DOM2_LAT_REG, 0x40);
IO_WRITE(PI_BSD_DOM2_PWD_REG, 0x12);
IO_WRITE(PI_BSD_DOM2_PGS_REG, 0x07);
IO_WRITE(PI_BSD_DOM2_RLS_REG, 0x03);
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
return 1;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int getSRAM32( uint8_t *buffer) {
return getSRAM(buffer, SAVE_SIZE_SRAM);
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int getSRAM128( uint8_t *buffer) {
return getSRAM(buffer, SAVE_SIZE_SRAM128);
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
//working hurray :D
int getEeprom4k( uint8_t *buffer) {
if(eeprom_present()){
int blocks=SAVE_SIZE_EEP4k/8;
for( int b = 0; b < blocks; b++ ) {
eeprom_read( b, &buffer[b * 8] );
}
return 1;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
return 0;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int getEeprom16k( uint8_t *buffer){
int blocks=SAVE_SIZE_EEP16k/8;
for( int b = 0; b < blocks; b++ ) {
eeprom_read( b, &buffer[b * 8] );
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
return 1;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int getFlashRAM( uint8_t *buffer){
evd_setSaveType(SAVE_TYPE_SRAM128); //2
sleep(10);
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int s = getSRAM(buffer, SAVE_SIZE_SRAM128);
data_cache_hit_writeback_invalidate(buffer,SAVE_SIZE_SRAM128);
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
sleep(10);
evd_setSaveType(SAVE_TYPE_FLASH); //5
return 1;
2014-06-29 01:10:11 -04:00
}
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
/*
sram upload
2014-06-30 03:49:02 -04:00
*/
int setSRAM( uint8_t *buffer,int size){
//half working
PI_DMAWait();
//Timing
PI_Init_SRAM();
//Readmode
PI_Init();
data_cache_hit_writeback_invalidate(buffer,size);
while (dma_busy());
PI_DMAToSRAM(buffer, 0, size);
data_cache_hit_writeback_invalidate(buffer,size);
//Wait
PI_DMAWait();
//Restore evd Timing
setSDTiming();
return 1;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int setSRAM32( uint8_t *buffer){
return setSRAM(buffer, SAVE_SIZE_SRAM);
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int setSRAM128( uint8_t *buffer){
return setSRAM(buffer, SAVE_SIZE_SRAM128);
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
//working hurray :D
int setEeprom4k( uint8_t *buffer){
if(eeprom_present()){
int blocks=SAVE_SIZE_EEP4k/8;
for( int b = 0; b < blocks; b++ ) {
eeprom_write( b, &buffer[b * 8] );
}
return 1;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
return 0;
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int setEeprom16k(uint8_t *buffer){
int blocks=SAVE_SIZE_EEP16k/8;
for( int b = 0; b < blocks; b++ ) {
eeprom_write( b, &buffer[b * 8] );
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
return 1;
}
2014-06-29 01:10:11 -04:00
//isn't working nor finished
2014-06-30 03:49:02 -04:00
int setFlashRAM(uint8_t *buffer){
evd_setSaveType(SAVE_TYPE_SRAM128); //2
sleep(10);
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
int s = setSRAM(buffer, SAVE_SIZE_SRAM128);
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
evd_setSaveType(SAVE_TYPE_FLASH); //5
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
return 1;
2014-06-29 01:10:11 -04:00
}
void setSDTiming(void){
/*
PI_BSD_DOM1_LAT_REG (0x04600014) write word 0x000000FF
PI_BSD_DOM1_PWD_REG (0x04600018) write word 0x000000FF
PI_BSD_DOM1_PGS_REG (0x0460001C) write word 0x0000000F
PI_BSD_DOM1_RLS_REG (0x04600020) write word 0x00000003
2014-06-30 03:49:02 -04:00
*
2014-06-29 01:10:11 -04:00
PI_BSD_DOM1_LAT_REG (0x04600014) write word 0x00000040
PI_BSD_DOM1_PWD_REG (0x04600018) write word 0x00803712
PI_BSD_DOM1_PGS_REG (0x0460001C) write word 0x00008037
PI_BSD_DOM1_RLS_REG (0x04600020) write word 0x00000803
*/
2014-06-30 03:49:02 -04:00
// PI_DMAWait();
IO_WRITE(PI_BSD_DOM1_LAT_REG, 0x40);
2014-06-29 01:10:11 -04:00
IO_WRITE(PI_BSD_DOM1_PWD_REG, 0x12);
IO_WRITE(PI_BSD_DOM1_PGS_REG, 0x07);
IO_WRITE(PI_BSD_DOM1_RLS_REG, 0x03);
IO_WRITE(PI_BSD_DOM2_LAT_REG, 0x40);
IO_WRITE(PI_BSD_DOM2_PWD_REG, 0x12);
IO_WRITE(PI_BSD_DOM2_PGS_REG, 0x07);
IO_WRITE(PI_BSD_DOM2_RLS_REG, 0x03);
}
void _data_cache_invalidate_all(void) {
2014-06-30 03:49:02 -04:00
asm(
"li $8,0x80000000;"
"li $9,0x80000000;"
"addu $9,$9,0x1FF0;"
"cacheloop:;"
"cache 1,0($8);"
"cache 1,16($8);"
"cache 1,32($8);"
"cache 1,48($8);"
"cache 1,64($8);"
"cache 1,80($8);"
"cache 1,96($8);"
"addu $8,$8,112;"
"bne $8,$9,cacheloop;"
"cache 1,0($8);"
2014-06-29 01:10:11 -04:00
: // no outputs
: // no inputs
: "$8", "$9" // trashed registers
2014-06-30 03:49:02 -04:00
);
2014-06-29 01:10:11 -04:00
}
2014-06-30 03:49:02 -04:00
void restoreTiming(void) {
//n64 timing restore :>
IO_WRITE(PI_BSD_DOM1_LAT_REG, 0x40);
2014-06-29 01:10:11 -04:00
IO_WRITE(PI_BSD_DOM1_PWD_REG, 0x12);
IO_WRITE(PI_BSD_DOM1_PGS_REG, 0x07);
IO_WRITE(PI_BSD_DOM1_RLS_REG, 0x03);
IO_WRITE(PI_BSD_DOM2_LAT_REG, 0x40);
IO_WRITE(PI_BSD_DOM2_PWD_REG, 0x12);
IO_WRITE(PI_BSD_DOM2_PGS_REG, 0x07);
IO_WRITE(PI_BSD_DOM2_RLS_REG, 0x03);
}
/*
* Load an image from the rom filesystem, returning a pointer to the
* sprite that hold the image.
*/
2014-06-30 03:49:02 -04:00
sprite_t *loadImageDFS(char *fname) {
2014-06-29 01:10:11 -04:00
int size, x, y, n, fd;
u8 *tbuf;
u8 *ibuf;
sprite_t *sbuf;
fd = dfs_open(fname);
if (fd < 0)
return 0; // couldn't open image
size = dfs_size(fd);
tbuf = malloc(size);
2014-06-30 03:49:02 -04:00
if (!tbuf) {
2014-06-29 01:10:11 -04:00
dfs_close(fd);
return 0; // out of memory
}
dfs_read(tbuf, 1, size, fd);
dfs_close(fd);
ibuf = stbi_load_from_memory(tbuf, size, &x, &y, &n, 4);
free(tbuf);
if (!ibuf)
return 0; // couldn't decode image
sbuf = (sprite_t*)malloc(sizeof(sprite_t) + x * y * 2);
2014-06-30 03:49:02 -04:00
if (!sbuf) {
2014-06-29 01:10:11 -04:00
stbi_image_free(ibuf);
return 0; // out of memory
}
sbuf->width = x;
sbuf->height = y;
sbuf->bitdepth = 2;
sbuf->format = 0;
sbuf->hslices = x / 32;
sbuf->vslices = y / 16;
color_t *src = (color_t*)ibuf;
u16 *dst = (u16*)((u32)sbuf + sizeof(sprite_t));
for (int j=0; j<y; j++)
for (int i=0; i<x; i++)
dst[i + j*x] = graphics_convert_color(src[i + j*x]) & 0x0000FFFF;
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
/* Invalidate data associated with sprite in cache */
data_cache_hit_writeback_invalidate( sbuf->data, sbuf->width * sbuf->height * sbuf->bitdepth );
stbi_image_free(ibuf);
return sbuf;
}
2014-06-30 03:49:02 -04:00
sprite_t *loadImage32(u8 *png, int size) {
2014-06-29 01:10:11 -04:00
int x, y, n, fd;
u8 *tbuf;
u32 *ibuf;
sprite_t *sbuf;
2014-06-30 03:49:02 -04:00
tbuf = malloc(size);
memcpy(tbuf,png,size);
2014-06-30 03:57:17 -04:00
ibuf = (u32*)stbi_load_from_memory(tbuf, size, &x, &y, &n, 4);
2014-06-29 01:10:11 -04:00
free(tbuf);
if (!ibuf)
return 0; // couldn't decode image
sbuf = (sprite_t*)malloc(sizeof(sprite_t) + x * y * 4);
2014-06-30 03:49:02 -04:00
if (!sbuf) {
2014-06-29 01:10:11 -04:00
stbi_image_free(ibuf);
return 0; // out of memory
}
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
sbuf->width = x;
sbuf->height = y;
sbuf->bitdepth = 4;
sbuf->format = 0;
sbuf->hslices = x / 32;
sbuf->vslices = y / 32;
// color_t *src = (color_t*)ibuf;
u32 *dst = (u32*)((u32)sbuf + sizeof(sprite_t));
for (int j=0; j<y; j++)
for (int i=0; i<x; i++)
dst[i + j*x] = ibuf[i + j*x];
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
/* Invalidate data associated with sprite in cache */
data_cache_hit_writeback_invalidate( sbuf->data, sbuf->width * sbuf->height * sbuf->bitdepth );
stbi_image_free(ibuf);
return sbuf;
}
2014-06-30 03:49:02 -04:00
sprite_t *loadImage32DFS(char *fname) {
2014-06-29 01:10:11 -04:00
int size, x, y, n, fd;
u8 *tbuf;
u32 *ibuf;
sprite_t *sbuf;
fd = dfs_open(fname);
if (fd < 0)
return 0; // couldn't open image
size = dfs_size(fd);
tbuf = malloc(size);
2014-06-30 03:49:02 -04:00
if (!tbuf) {
2014-06-29 01:10:11 -04:00
dfs_close(fd);
return 0; // out of memory
}
dfs_read(tbuf, 1, size, fd);
dfs_close(fd);
2014-06-30 03:57:17 -04:00
ibuf = (u32*)stbi_load_from_memory(tbuf, size, &x, &y, &n, 4);
2014-06-29 01:10:11 -04:00
free(tbuf);
if (!ibuf)
return 0; // couldn't decode image
sbuf = (sprite_t*)malloc(sizeof(sprite_t) + x * y * 4);
2014-06-30 03:49:02 -04:00
if (!sbuf) {
2014-06-29 01:10:11 -04:00
stbi_image_free(ibuf);
return 0; // out of memory
}
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
sbuf->width = x;
sbuf->height = y;
sbuf->bitdepth = 4;
sbuf->format = 0;
sbuf->hslices = x / 32;
sbuf->vslices = y / 32;
// color_t *src = (color_t*)ibuf;
u32 *dst = (u32*)((u32)sbuf + sizeof(sprite_t));
for (int j=0; j<y; j++)
for (int i=0; i<x; i++)
dst[i + j*x] = ibuf[i + j*x];
2014-06-30 03:49:02 -04:00
2014-06-29 01:10:11 -04:00
/* Invalidate data associated with sprite in cache */
data_cache_hit_writeback_invalidate( sbuf->data, sbuf->width * sbuf->height * sbuf->bitdepth );
stbi_image_free(ibuf);
return sbuf;
}
/*
* Draw an image to the screen using the sprite passed.
*/
2014-06-30 03:49:02 -04:00
void drawImage(display_context_t dcon, sprite_t *sprite) {
2014-06-29 01:10:11 -04:00
int x, y = 0;
rdp_sync(SYNC_PIPE);
rdp_set_default_clipping();
rdp_enable_texture_copy();
rdp_attach_display(dcon);
// Draw image
2014-06-30 03:49:02 -04:00
for (int j=0; j<sprite->vslices; j++) {
2014-06-29 01:10:11 -04:00
x = 0;
2014-06-30 03:49:02 -04:00
for (int i=0; i<sprite->hslices; i++) {
2014-06-29 01:10:11 -04:00
rdp_sync(SYNC_PIPE);
rdp_load_texture_stride(0, 0, MIRROR_DISABLED, sprite, j*sprite->hslices + i);
rdp_draw_sprite(0, x, y);
x += 32;
}
y += 16;
}
rdp_detach_display();
}
#define CIC_6101 1
#define CIC_6102 2
#define CIC_6103 3
#define CIC_6104 4
#define CIC_6105 5
#define CIC_6106 6
#define ROM ((vu32 *)0xB0000000)
#define EXE_START 0xB0001000
#define EXE_SIZE 0x00100004
#define TV_TYPE *(vu32 *)0x80000300
#define RAM_SIZE_1 *(vu32 *)0x80000318
#define RAM_SIZE_2 *(vu32 *)0x800003F0
2014-06-29 01:10:11 -04:00
void simulate_boot(u32 cic_chip, u8 gBootCic, u32 *cheat_lists[2]) {
// Clear screen
IO_WRITE(VI_V_INT, 0x3FF);
IO_WRITE(VI_H_LIMITS, 0);
IO_WRITE(VI_CUR_LINE, 0);
2014-06-29 01:10:11 -04:00
// Reset controller and clear interrupt
IO_WRITE(PI_STATUS_REG, 0x03);
2014-06-29 01:10:11 -04:00
// Set cart latency registers with values specified in ROM
u32 lat = ROM[0];
IO_WRITE(PI_BSD_DOM1_LAT_REG, lat & 0xFF);
IO_WRITE(PI_BSD_DOM1_PWD_REG, lat >> 8);
IO_WRITE(PI_BSD_DOM1_PGS_REG, lat >> 16);
IO_WRITE(PI_BSD_DOM1_RLS_REG, lat >> 20);
// Fix RAM size location (State required by CIC-NUS-6105)
vu32 *ram_size = (cic_chip == CIC_6105) ? &RAM_SIZE_2 : &RAM_SIZE_1;
*ram_size = (gBootCic == CIC_6105) ? RAM_SIZE_2 : RAM_SIZE_1;
if (force_tv) {
/*
* This magic bit-twiddling is required to retain backward compatibility
* with old ini files. It converts alt64's tv mode to N64's tv mode:
* alt64:
* 0: Use default
* 1: Force NTSC
* 2: Force PAL
* 3: Force M-PAL
* N64:
* 0: PAL
* 1: NTSC
* 2: M-PAL
* 3: Unused
*/
TV_TYPE = MIN((~force_tv - 1) & 3, 2);
2014-06-30 03:49:02 -04:00
}
2014-06-29 01:10:11 -04:00
2014-06-30 03:49:02 -04:00
if (gCheats) {
// Copy patcher into a memory location where it will not be overwritten
void *patcher = (void*)0x80700000; // Temporary patcher location
u32 patcher_length = &&patcher_end - &&patcher_start;
memcpy(patcher, &&patcher_start, patcher_length);
// Copy code lists into memory, behind the patcher
u32 *p = patcher + patcher_length;
*(u32**)0xA06FFFFC = p; // Save temporary code list location
for (int i = 0; i < 2; i++) {
int j = -2;
do {
j += 2;
patcher_length += 8;
*p++ = cheat_lists[i][j];
*p++ = cheat_lists[i][j + 1];
} while (cheat_lists[i][j] || cheat_lists[i][j + 1]);
2014-06-29 01:10:11 -04:00
}
// Write cache to physical memory and invalidate
data_cache_hit_writeback(patcher, patcher_length);
inst_cache_hit_invalidate(patcher, patcher_length);
2014-06-29 01:10:11 -04:00
}
// Start game via CIC boot code
asm __volatile__ (
".set noreorder;"
"lui $t0, 0x8000;"
// State required by all CICs
"move $s3, $zero;" // osRomType (0: N64, 1: 64DD)
"lw $s4, 0x0300($t0);" // osTvType (0: PAL, 1: NTSC, 2: MPAL)
"move $s5, $zero;" // osResetType (0: Cold, 1: NMI)
"lui $s6, %%hi(cic_ids);" // osCicId (See cic_ids LUT)
"addu $s6, $s6, %0;"
"lbu $s6, %%lo(cic_ids)($s6);"
"lw $s7, 0x0314($t0);" // osVersion
// Copy PIF code to RSP IMEM (State required by CIC-NUS-6105)
"lui $a0, 0xA400;"
"lui $a1, %%hi(imem_start);"
"ori $a2, $zero, 0x0008;"
"1:"
"lw $t0, %%lo(imem_start)($a1);"
"addiu $a1, $a1, 4;"
"sw $t0, 0x1000($a0);"
"addiu $a2, $a2, -1;"
"bnez $a2, 1b;"
"addiu $a0, $a0, 4;"
// Copy CIC boot code to RSP DMEM
"lui $t3, 0xA400;"
"ori $t3, $t3, 0x0040;" // State required by CIC-NUS-6105
"move $a0, $t3;"
"lui $a1, 0xB000;"
"ori $a2, 0x0FC0;"
"1:"
"lw $t0, 0x0040($a1);"
"addiu $a1, $a1, 4;"
"sw $t0, 0x0000($a0);"
"addiu $a2, $a2, -4;"
"bnez $a2, 1b;"
"addiu $a0, $a0, 4;"
// Boot with or without cheats enabled?
"beqz %1, 2f;"
// Patch CIC boot code
"lui $a1, %%hi(cic_patch_offsets);"
"addu $a1, $a1, %0;"
"lbu $a1, %%lo(cic_patch_offsets)($a1);"
"addu $a0, $t3, $a1;"
"lui $a1, 0x081C;" // "j 0x80700000"
"ori $a2, $zero, 0x06;"
"bne %0, $a2, 1f;"
"lui $a2, 0x8188;"
"ori $a2, $a2, 0x764A;"
"xor $a1, $a1, $a2;" // CIC-NUS-6106 encryption
"1:"
"sw $a1, 0x0700($a0);" // Patch CIC boot code with jump
// Patch CIC boot code to disable checksum failure halt
// Required for CIC-NUS-6105
"ori $a2, $zero, 0x05;"
"beql %0, $a2, 2f;"
"sw $zero, 0x06CC($a0);"
// Go!
"2:"
"lui $sp, 0xA400;"
"ori $ra, $sp, 0x1550;" // State required by CIC-NUS-6105
"jr $t3;"
"ori $sp, $sp, 0x1FF0;" // State required by CIC-NUS-6105
// Table of all CIC IDs
"cic_ids:"
".byte 0x00;" // Unused
".byte 0x3F;" // NUS-CIC-6101
".byte 0x3F;" // NUS-CIC-6102
".byte 0x78;" // NUS-CIC-6103
".byte 0x00;" // Unused
".byte 0x91;" // NUS-CIC-6105
".byte 0x85;" // NUS-CIC-6106
".byte 0x00;" // Unused
"cic_patch_offsets:"
".byte 0x00;" // Unused
".byte 0x30;" // CIC-NUS-6101
".byte 0x2C;" // CIC-NUS-6102
".byte 0x20;" // CIC-NUS-6103
".byte 0x00;" // Unused
".byte 0x8C;" // CIC-NUS-6105
".byte 0x60;" // CIC-NUS-6106
".byte 0x00;" // Unused
// These instructions are copied to RSP IMEM; we don't execute them.
"imem_start:"
"lui $t5, 0xBFC0;"
"1:"
"lw $t0, 0x07FC($t5);"
"addiu $t5, $t5, 0x07C0;"
"andi $t0, $t0, 0x0080;"
"bnezl $t0, 1b;"
"lui $t5, 0xBFC0;"
"lw $t0, 0x0024($t5);"
"lui $t3, 0xB000;"
: // outputs
: "r" (cic_chip), // inputs
"r" (gCheats)
: "$4", "$5", "$6", "$8", // clobber
"$11", "$19", "$20", "$21",
"$22", "$23", "memory"
);
2014-06-29 01:10:11 -04:00
patcher_start:
asm __volatile__ (
".set noat;"
".set noreorder;"
// Installs general exception handler, router, and code engine
"patcher:"
"lui $t5, 0x8070;" // Start of temporary patcher location
"lui $t6, 0x8000;" // Start of cached memory
"li $t7, 0x007FFFFF;" // Address mask
"li $t8, 0x807C5C00;" // Permanent code engine location
// "lw $t9, 0xFFFC($t5);" // Assembles incorrectly (gcc sucks)
"addiu $t9, $t5, -4;" // Go to hell, gcc!
"lw $t9, 0x0000($t9);" // Get temporary code lists location
"1:"
// Apply boot-time cheats
"lw $v0, 0x0000($t9);"
"bnez $v0, 2f;"
"lw $v1, 0x0004($t9);"
"beqz $v1, 1f;"
"2:"
"addiu $t9, $t9, 0x0008;"
"srl $t2, $v0, 24;"
"li $at, 0xEE;"
"beq $t2, $at, 5f;"
"li $at, 0xF0;"
"and $v0, $v0, $t7;"
"beq $t2, $at, 4f;"
"or $v0, $v0, $t6;"
"li $at, 0xF1;"
"beq $t2, $at, 3f;"
"nop;"
// Apply FF code type
"li $at, 0xFFFFFFFC;" // Mask address
"b 1b;"
"and $t8, $v0, $at;" // Update permanent code engine location
"3:"
// Apply F1 code type
"b 1b;"
"sh $v1, 0x0000($v0);"
"4:"
// Apply F0 code type
"b 1b;"
"sb $v1, 0x0000($v0);"
"5:"
// Apply EE code type
"lui $v0, 0x0040;"
"sw $v0, 0x0318($t6);"
"b 1b;"
"sw $v0, 0x03F0($t6);"
"1:"
// Install General Exception Handler
"srl $at, $t8, 2;"
"and $v0, $at, $t7;"
"lui $at, 0x0800;"
"or $v0, $v0, $at;" // Jump to code engine
"sw $v0, 0x0180($t6);"
"sw $zero, 0x0184($t6);"
// Install code engine to permanent location
"sw $t8, 0x0188($t6);" // Save permanent code engine location
"la $at, %%lo(patcher);"
"la $v0, %%lo(code_engine_start);"
"la $v1, %%lo(code_engine_end);"
"subu $at, $v0, $at;" // Get patcher length
"subu $v1, $v1, $v0;" // Get code engine length
"addu $v0, $t5, $at;" // Get temporary code engine location
"1:"
"lw $at, 0x0000($v0);"
"addiu $v1, $v1, -4;"
"sw $at, 0x0000($t8);"
"addiu $v0, $v0, 4;"
"bgtz $v1, 1b;"
"addiu $t8, $t8, 4;"
"sw $t8, 0x018C($t6);" // Save permanent code list location
"1:"
// Install in-game code list
"lw $v0, 0x0000($t9);"
"lw $v1, 0x0004($t9);"
"addiu $t9, $t9, 8;"
"sw $v0, 0x0000($t8);"
"sw $v1, 0x0004($t8);"
"bnez $v0, 1b;"
"addiu $t8, $t8, 8;"
"bnez $v1, 1b;"
"nop;"
// Write cache to physical memory and invalidate (GEH)
"ori $t0, $t6, 0x0180;"
"li $at, 0x0010;"
"1:"
"cache 0x19, 0x0000($t0);" // Data cache hit writeback
"cache 0x10, 0x0000($t0);" // Instruction cache hit invalidate
"addiu $at, $at, -4;"
"bnez $at, 1b;"
"addiu $t0, $t0, 4;"
// Write cache to physical memory and invalidate (code engine + list)
"lw $t0, 0x0188($t6);"
"subu $at, $t8, $t0;"
"1:"
"cache 0x19, 0x0000($t0);" // Data cache hit writeback
"cache 0x10, 0x0000($t0);" // Instruction cache hit invalidate
"addiu $at, $at, -4;"
"bnez $at, 1b;"
"addiu $t0, $t0, 4;"
// Protect GEH via WatchLo/WatchHi
"li $t0, 0x0181;" // Watch 0x80000180 for writes
"mtc0 $t0, $18;" // Cp0 WatchLo
"nop;"
"mtc0 $zero, $19;" // Cp0 WatchHi
// Start game!
"jr $t1;"
"nop;"
"code_engine_start:"
"mfc0 $k0, $13;" // Cp0 Cause
"andi $k1, $k0, 0x1000;" // Pre-NMI
"bnezl $k1, 1f;"
"mtc0 $zero, $18;" // Cp0 WatchLo
"1:"
"andi $k0, $k0, 0x7C;"
"li $k1, 0x5C;" // Watchpoint
"bne $k0, $k1, 1f;"
// Watch exception; manipulate register contents
"mfc0 $k1, $14;" // Cp0 EPC
"lw $k1, 0x0000($k1);" // Load cause instruction
"lui $k0, 0x03E0;"
"and $k1, $k1, $k0;" // Mask (base) register
"srl $k1, $k1, 5;" // Shift it to the "rt" position
"lui $k0, 0x3740;" // Upper half "ori $zero, $k0, 0x0120"
"or $k1, $k1, $k0;"
"ori $k1, $k1, 0x0120;" // Lower half "ori $zero, $k0, 0x0120"
"lui $k0, 0x8000;"
"lw $k0, 0x0188($k0);" // Load permanent code engine location
"sw $k1, 0x0060($k0);" // Self-modifying code FTW!
"cache 0x19, 0x0060($k0);" // Data cache hit writeback
"cache 0x10, 0x0060($k0);" // Instruction cache hit invalidate
"lui $k0, 0x8000;"
"nop;" // Short delay for cache sync
"nop;"
"nop;"
"nop;" // Placeholder for self-modifying code
"eret;" // Back to game
"1:"
// Run code engine
"lui $k0, 0x8000;"
"lw $k0, 0x0188($k0);"
"addiu $k0, $k0, -0x28;"
"sd $v1, 0x0000($k0);"
"sd $v0, 0x0008($k0);"
"sd $t9, 0x0010($k0);"
"sd $t8, 0x0018($k0);"
"sd $t7, 0x0020($k0);"
// Handle cheats
"lui $t9, 0x8000;"
"lw $t9, 0x018C($t9);" // Get code list location
"1:"
"lw $v0, 0x0000($t9);" // Load address
"bnez $v0, 2f;"
"lw $v1, 0x0004($t9);" // Load value
"beqz $v1, 4f;"
"nop;"
// Address == 0 (TODO)
"b 1b;"
"2:"
// Address != 0
"addiu $t9, $t9, 0x0008;"
"srl $t7, $v0, 24;"
"sltiu $k1, $t7, 0xD0;" // Code type < 0xD0 ?
"sltiu $t8, $t7, 0xD4;" // Code type < 0xD4 ?
"xor $k1, $k1, $t8;" // $k1 = (0xD0 >= code_type < 0xD4)
"li $t8, 0x50;"
"bne $t7, $t8, 3f;" // Code type != 0x50 ? -> 3
// GS Patch/Repeater
"srl $t8, $v0, 8;"
"andi $t8, $t8, 0x00FF;" // Get address count
"andi $t7, $v0, 0x00FF;" // Get address increment
"lw $v0, 0x0000($t9);" // Load address
"lw $k1, 0x0004($t9);" // Load value
"addiu $t9, $t9, 0x0008;"
"2:"
"sh $k1, 0x0000($v0);" // Repeater/Patch write
"addiu $t8, $t8, -1;"
"addu $v0, $v0, $t7;"
"bnez $t8, 2b;"
"addu $k1, $k1, $v1;"
"b 1b;"
"3:"
// GS RAM write or Conditional
"lui $t7, 0x0300;"
"and $t7, $v0, $t7;" // Test for 8-bit or 16-bit code type
"li $t8, 0xA07FFFFF;"
"and $v0, $v0, $t8;"
"lui $t8, 0x8000;"
"beqz $k1, 2f;"
"or $v0, $v0, $t8;" // Mask address
// GS Conditional
"sll $k1, $t7, 7;"
"beqzl $k1, 3f;"
"lbu $t8, 0x0000($v0);" // 8-bit conditional
"lhu $t8, 0x0000($v0);" // 16-bit conditional
"3:"
"srl $t7, $t7, 22;"
"andi $t7, $t7, 8;" // Test for equal-to or not-equal-to
"beql $v1, $t8, 1b;"
"add $t9, $t9, $t7;" // Add if equal
"xori $t7, $t7, 8;"
"b 1b;"
"add $t9, $t9, $t7;" // Add if not-equal
"2:"
// GS RAM write
"sll $k1, $t7, 7;"
"beqzl $k1, 3f;"
"sb $v1, 0x0000($v0);" // Constant 8-bit write
"sh $v1, 0x0000($v0);" // Constant 16-bit write
"3:"
"b 1b;"
"4:"
// Restore registers from our temporary stack, and back to the game!
"ld $t7, 0x0020($k0);"
"ld $t8, 0x0018($k0);"
"ld $t9, 0x0010($k0);"
"ld $v0, 0x0008($k0);"
"j 0x80000120;"
"ld $v1, 0x0000($k0);"
"code_engine_end:"
: // outputs
: // inputs
: "memory" // clobber
);
patcher_end:
2014-06-30 03:49:02 -04:00
return;
2014-06-29 01:10:11 -04:00
}