(Disc offset, Size)
The complete list of disc reads for AR 1.14A is
(0x00000000, 0x00100000)
(0x50000000, 0x00100000)
(0x50100000, 0x00100000)
(0x50200000, 0x00100000)
(0x50300000, 0x00100000)
The first one and the last one complete successfully, even if they don't need to read the full amount.
Checksums for my disc (can't guarantee these are correct):
CRC32: 1343B270
MD5: 20CFC5F87ACA12C1E983B23D6145EDAF
SHA-1: BEA42EE605A7878EEE608AED314432CEBAD20CC6
The complete list of disc reads for AGP is
(0x00000000, 0x00100000)
(0x50000000, 0x00100000)
(0x12D00000, 0x00100000]
(0x12E00000, 0x00100000]
(0x12B00000, 0x00100000)
(0x12C00000, 0x00100000)
(0x09900000, 0x00100000)
(0x0A900000, 0x00100000)
(0x0B100000, 0x00100000)
(0x0B900000, 0x00100000)
(0x0C100000, 0x00100000)
(0x0D900000, 0x00100000)
(0x0E100000, 0x00100000)
(0x0E900000, 0x00100000)
(0x0F900000, 0x00100000)
(0x0F100000, 0x00100000)
The first two complete successfully, even if they don't need to read the full amount.
Checksums for my disc (can't guarantee these are correct):
CRC32: 5B478075
MD5: 0F9710ACA393CF6EFDB1094B96386F3F
SHA-1: BC6DFEA0577A7C5455473C63362330816CAB4792
For AGP:
Hash is calculated one byte at a time. Start with 0xFF, then apply the command byte (skipping 0xAE). Then apply one byte in order for each of the letter pairs below (XX,YY,ZZ,VV) including both writes and reads, except for the hash itself. WriteCmd(0, 1) are skipped in hash calculations.
The 0x100 bytes from the disc that are used in the hash function to verify the values from the AGP are at 0x12befa4c in the disc (starts with 0x00 and ends with 0x35).
Before commands, ensure device present
Disc calls AE00, then AE01. Then calls AE02 0x60 times each at 0x0, 0x100000, 0x200000, 0x400000... etc. to find the size (repeats or returns 0000, 0001, 0002,... not byte swapped). All data greater than 8bits from the cartridge (not necessarily all data from the AGP = Init results, Hash,etc.) is byte swapped.
Once it has the size, it calls AE03 repeatedly to read the ROM. The game then will read and write to the Save area when it wants to using the other commands. See http://problemkaputt.de/gbatek.htm#gbacartbackupeeprom for specifics. Where this link says to read 0xDFFFF00 after writing, it looks like the disc code calls AE02(with offset 0x800000). I assume this accomplishes the same thing.
1MHz = 0, 8MHz = 0x30, 16MHz = 0x40
StatusEnable(ClockCode) => *(0xCC006814) = 0x80 | ClockCode
StatusDisable => *(0xCC006814) = 0x0
ApplyHash(Data)
=> Hash = HashArray12befa4cArray[CurrentHash ^ Data] // all values are single bytes
WriteImm(Data, Bytes) => *(0xCC006824) = (Data << ((4-Bytes)*8))
=> *(0xCC006820) = 5 | ((Bytes - 1)>>4)
=> Wait for *(0xCC006820) & 0x1 == 0
=> Recommend additional WaitCycles(475) for timing
ReadImm(Data, Bytes) => *(0xCC006820) = 1 | ((Bytes - 1)>>4)
=> Wait for *(0xCC006820) & 0x1 == 0
=> Data = *(0xCC006824) >> ((4-Bytes)*8))
WaitCycles(Cycles) => Number of GameCube cycles to wait
DevicePresent() => (*(CC006814) & 0x1000 == 0x1000) Must be true
=> WaitCycles(0x6800) 1000 times
=> *(CC006814) = 0x800
=> WaitCycles(0x6800) 1000 times
Command processing code starts at 800539D4
AE00 = (Clock Handshake?)
StatusEnable(1MHz)
WriteImm(0xAE00, 2)
WriteImm(0x00, 1)
ReadImm(0xXXXXXXYY, 4): 0xXXXXXX=0x5AAA55, 0xYY = Hash = 0x17
StatusDisable()
AE01 = (Init?)
StatusEnable(8MHz)
WriteImm(0xAE01, 2)
WriteImm(0x00, 1)
ReadImm(0xXXXXXXXX, 4): 0xXXXXXXXX=0x01020304 (always?)
ReadImm(0xYY, 1): 0xYY = Hash = 0xF0
StatusDisable()
AE02 = Read16bit at Offset (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE02, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXXXX, 3): 0xXXXXXX=Offset (+1 = 16-bits)
Wait(475 cycles)
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xYYYYZZ, 3): 0xYYYY=16 bits read (byte swapped), 0xZZ=Hash
StatusDisable()
AE03 = Read 0x10000 sequential 16bit values at Offset (Reads Offset byte-swapped if no cartridge)
StatusEnable(16MHz)
WriteImm(0xAE03, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXXXX, 3): 0xXXXXXX=Offset (+1 = 16-bits)
WriteImm(0x00, 1)
Repeated 0x8000 times
ReadImm(0xYYYYZZZZ, 4): 0xYYYY=16 bits read (byte swapped), 0xZZZZ=16 bits read (byte swapped)
ReadImm(0xVV, 1): 0xVV = Hash??? Doesn't seem to match. Not used. Always 0xFF
StatusDisable()
AE04 = Single Byte Read (Used for SRAM and flash) (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE04, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXX, 2): 0xXXXX=Offset
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xYYZZ, 2): 0xYY=Value, 0xZZ=Hash
StatusDisable()
AE06 = 16-bit Write? (used for ?? Possibly GBA Cart I/O Port (GPIO)??)
StatusEnable(16MHz)
WriteImm(0xAE06, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXXXX, 3): 0xXXXXXX=Offset??
WriteImm(0xYYYY, 2): 0xYYYY=Data??
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xZZ, 1): 0xZZ=Hash
StatusDisable()
AE07 = Single Byte Write (used for SRAM and Flash) (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE07, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXXYY, 3): 0xXXXX=Offset, 0xYY=Write Value
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xZZ, 1): 0xZZ=Hash
StatusDisable()
AE09 = Start DMA
StatusEnable(16MHz)
WriteImm(0xAE09, 2)
StatusDisable()
AE0A = End DMA (and update write busy status?)
StatusEnable(16MHz)
WriteImm(0xAE0A, 2)
StatusDisable()
AE0B = Read DMA (used for EEPROM - only 0x0100 bit as read is valid [= 0x0001 value wtih byte-swap correction]) (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE0B, 2)
Wait(475 cycles)
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xXXXXYY, 3): 0xXXXX=16-bit read (byte-swapped), 0xYY=Hash
StatusDisable()
AE0C = Write DMA (used for EEPROM - only 0x0001 bit from data used) (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE0C, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXX, 2): 0xXXXX=16 bit data to write
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xYY, 1): 0xYY=Hash
StatusDisable()
Example code (borrowed format from CleanRip):
Code: Select all
/**
* CleanRip - gc_agp.c (originally from Cube64/Wii64)
* Copyright (C) 2007, 2008, 2009, 2010 emu_kidid
*
* DVD Reading support for GC/Wii
*
* CleanRip homepage: http://code.google.com/p/cleanrip
* email address: emukidid@gmail.com
*
*
* This program is free software; you can redistribute it and/
* or modify it under the terms of the GNU General Public Li-
* cence as published by the Free Software Foundation; either
* version 2 of the Licence, or any later version.
*
* This program is distributed in the hope that it will be use-
* ful, but WITHOUT ANY WARRANTY; without even the implied war-
* ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public Licence for more details.
*
**/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ogc/dvd.h>
#include <malloc.h>
#include <string.h>
#include <gccore.h>
#include <unistd.h>
#include <di/di.h>
#include <ogc/machine/processor.h>
#include "gc_dvd.h"
#include "main.h"
#ifdef HW_RVL
volatile u32* agp = (volatile u32*)0xCD806800;
#else
volatile u32* agp = (volatile u32*)0xCC006800;
#endif
enum
{
Clock01MHz = 0x00,
Clock08MHz = 0x30,
Clock16MHz = 0x40
};
enum
{
NO_ERROR = 0,
AGP_NOT_PRESENT = 0x10,
UNEXPECTED_VALUE = 0x20,
HASH_FAIL = 0x40,
BAD_PARAM = 0x80,
} ErrorCodes;
#define StatusDev0 (0x80)
#define StatusIntAck (0x405)
#define ReadCode (0x1)
#define WriteCode (0x5)
#define StatusBusy (0x1)
void WaitGC_Cycles(u32 Cycles)
{
usleep((Cycles + 484) / 485); //round up
}
bool DoVerify = false;
u8 *HashArray = 0;
#define InitialHash (0xFF)
u8 Hash = InitialHash;
u8 DoHash(u8 Data)
{
Hash = Hash ^ Data;
Hash = HashArray[Hash];
return Hash;
}
u8 DoHash32(u32 Data, u32 Byte)
{
// Byte 0 = LSB
return DoHash((Data >> ((Byte) * 8)) & 0xFF);
}
u8 DoHashArray(u8* Data, u32 Length)
{
u32 Index;
for (Index = 0; Index < Length; Index++)
DoHash(Data[Index]);
return Hash;
}
void UpdateStatus(u32 StatusAdd)
{
u32 Status = agp[0];
Status = (Status & StatusIntAck) | StatusAdd;
agp[0] = Status;
}
void WriteCmd(u32 Cmd, u32 Bytes)
{
u32 ByteCode = ((Bytes - 1) & 0x3) << 4;
Cmd = Cmd << ((4 - Bytes) * 8);
agp[4] = Cmd;
agp[3] = ByteCode | WriteCode;
u32 Status = StatusBusy;
while (Status & StatusBusy)
Status = agp[3];
WaitGC_Cycles(475);
}
u32 ReadCmd(u32 Bytes)
{
u32 ByteCode = ((Bytes - 1) & 0x3) << 4;
agp[3] = ByteCode | ReadCode;
u32 Status = StatusBusy;
while (Status & StatusBusy)
Status = agp[3];
return agp[4] >> ((4 - Bytes) * 8);
}
void DataCopy(u8* Dst, u8* Src, u32 Length, bool ByteSwapped)
{
u32 i;
for (i = 0; i < Length; i++)
{
if (ByteSwapped && ((i + 1) < Length))
{
Dst[i] = Src[i + 1];
i++;
Dst[i] = Src[i - 1];
}
else
Dst[i] = Src[i];
}
}
u32 DoCmd(u8 Cmd, u32 Param1, u32 Param2, u8* Result)
{
u32 ImmCmd = 0xAE00 | Cmd;
u32 Clock = Clock16MHz;
if (Cmd == 0)
Clock = Clock01MHz;
else if (Cmd == 1)
Clock = Clock08MHz;
u32 ReturnVal = HASH_FAIL;
u32 ReadData;
u32 i;
u8* ReadPtr = (u8*)&ReadData;
u32 Try;
u32 NumTries = 1;
switch (Cmd)
{
default:
NumTries = 1;
break;
case 0x2:
case 0x4:
case 0x7:
case 0xB:
case 0xC:
NumTries = 4;
break;
}
for (Try = 0; (Try < NumTries) && (ReturnVal != NO_ERROR); Try++)
{
ReturnVal = NO_ERROR;
UpdateStatus(StatusDev0 | Clock);
Hash = InitialHash;
WriteCmd(ImmCmd, 2);
DoHash(Cmd); //skip 0xAE
switch (Cmd)
{
case 0x0:
WriteCmd(0, 1);
ReadData = ReadCmd(4);
DataCopy(Result, ReadPtr, 3, false);
if (ReadData != 0x5AAA5517)
ReturnVal = UNEXPECTED_VALUE | Cmd;
break;
case 0x1:
WriteCmd(0, 1);
ReadData = ReadCmd(4);
DataCopy(Result, ReadPtr, 4, false);
if (ReadData != 0x01020304)
ReturnVal = UNEXPECTED_VALUE | Cmd;
ReadData = ReadCmd(1);
if ((ReadData & 0xFF) != 0xF0)
ReturnVal = HASH_FAIL | Cmd;
break;
case 0x2:
WriteCmd(0, 1);
WriteCmd(Param1, 3);
WaitGC_Cycles(475);
WriteCmd(0, 1);
ReadData = ReadCmd(3);
DataCopy(Result, ReadPtr + 1, 2, true);
if (DoVerify)
{
DoHash32(Param1, 2);
DoHash32(Param1, 1);
DoHash32(Param1, 0);
DoHash32(ReadData, 2);
DoHash32(ReadData, 1);
if (Hash != (ReadData & 0xFF))
ReturnVal = HASH_FAIL | Cmd;
}
break;
case 0x3:
WriteCmd(0, 1);
WriteCmd(Param1, 3);
WaitGC_Cycles(475); // Not in disc code
WriteCmd(0, 1);
for (i = 0; i < 0x8000; i++)
{
ReadData = ReadCmd(4);
DataCopy(Result, ReadPtr, 4, true);
Result += 4;
}
ReadData = ReadCmd(1);
if (0xFF != (ReadData & 0xFF))
ReturnVal = UNEXPECTED_VALUE | Cmd;
break;
case 0x4:
WriteCmd(0, 1);
WriteCmd(Param1, 2);
WaitGC_Cycles(475);
WriteCmd(0, 1);
ReadData = ReadCmd(2);
DataCopy(Result, ReadPtr + 2, 1, true);
if (DoVerify)
{
DoHash32(Param1, 1);
DoHash32(Param1, 0);
DoHash32(ReadData, 1);
if (Hash != (ReadData & 0xFF))
ReturnVal = (ReadData << 16) | (Hash << 8) | HASH_FAIL | Cmd;
}
break;
case 0x6:
WriteCmd(0, 1);
WriteCmd(Param1, 3);
WriteCmd(Param2, 2);
WaitGC_Cycles(475);
WriteCmd(0, 1);
ReadData = ReadCmd(1);
if (DoVerify)
{
DoHash32(Param1, 2);
DoHash32(Param1, 1);
DoHash32(Param1, 0);
DoHash32(Param2, 1);
DoHash32(Param2, 0);
if (Hash != (ReadData & 0xFF))
ReturnVal = HASH_FAIL | Cmd;
}
break;
case 0x7:
WriteCmd(0, 1);
WriteCmd(Param1 << 8 | Param2, 3);
WaitGC_Cycles(475);
WriteCmd(0, 1);
ReadData = ReadCmd(1);
if (DoVerify)
{
DoHash32(Param1, 1);
DoHash32(Param1, 0);
DoHash32(Param2, 0);
if (Hash != (ReadData & 0xFF))
ReturnVal = HASH_FAIL | Cmd;
}
break;
case 0x9:
case 0xA:
break;
case 0xB:
WaitGC_Cycles(475);
WaitGC_Cycles(475);
WriteCmd(0, 1);
ReadData = ReadCmd(3);
DataCopy(Result, ReadPtr + 1, 2, true);
if (DoVerify)
{
DoHash32(ReadData, 2);
DoHash32(ReadData, 1);
if (Hash != (ReadData & 0xFF))
ReturnVal = HASH_FAIL | Cmd;
}
// DataCopy(Result, ReadPtr, 4, false);
// WaitGC_Cycles(475 * 10);
break;
case 0xC:
WriteCmd(0, 1);
WriteCmd(Param1, 2);
WaitGC_Cycles(475);
WriteCmd(0, 1);
ReadData = ReadCmd(1);
if (DoVerify)
{
DoHash32(Param1, 1);
DoHash32(Param1, 0);
if (Hash != (ReadData & 0xFF))
ReturnVal = HASH_FAIL | Cmd;
}
WaitGC_Cycles(475 * 12);
break;
}
UpdateStatus(0);
}
return ReturnVal;
}
int init_agp(u8 *HashArrayIn) {
HashArray = HashArrayIn;
if (HashArray[0xFF] != 0)
DoVerify = true;
u32 DevicePresent = 0x1000;
u32 DeviceInsertionInt = 0x800;
agp[0] = 0;
u32 Status = agp[0];
if ((Status & DevicePresent) == 0)
return AGP_NOT_PRESENT;
WaitGC_Cycles(1000 * 0x6800);
Status = agp[0];
Status |= DeviceInsertionInt;
agp[0] = Status;
WaitGC_Cycles(1000 * 0x6800);
u32 Return;
u32 Data;
Return = DoCmd(0, 0, 0, (u8*)&Data);
if (Return != NO_ERROR)
return Return;
Return = DoCmd(1, 0, 0, (u8*)&Data);
return Return;
}
/*
DVD_LowRead64(void* dst, unsigned int len, uint64_t offset)
Read Raw, needs to be on sector boundaries
Has 8,796,093,020,160 byte limit (8 TeraBytes)
Synchronous function.
return -1 if offset is out of range
*/
int AGP_LowRead64(void* dst, unsigned int len, uint64_t offset) {
if (offset + len > 0x2000000)
return BAD_PARAM;
if ((offset & 0x1) != 0x0)
return BAD_PARAM;
unsigned char* Buffer = dst;
u32 Offset = 0;
u32 Startoffset = offset >> 1;
for (; Offset + 0x10000 <= (len >> 1); Offset += 0x10000)
{
u32 RetVal = DoCmd(3, Offset + Startoffset, 0, Buffer + (Offset << 1));
if ((DoVerify) && (RetVal != NO_ERROR))
return RetVal;
}
for (; Offset < (len >> 1); Offset++)
{
u32 RetVal = DoCmd(2, Offset + Startoffset, 0, Buffer + (Offset << 1));
if ((DoVerify) && (RetVal != NO_ERROR))
return RetVal;
}
return NO_ERROR;
}
u32 GetEEprom(u8* dst, u32 Offset)
{
u32 i;
u32 Return;
Return = DoCmd(9, 0, 0, NULL);
if (Return == NO_ERROR)
Return = DoCmd(0xC, 1, 0, NULL);
if (Return == NO_ERROR)
Return = DoCmd(0xC, 1, 0, NULL);
for (i = 8; (i > 2) && (Return == NO_ERROR); i--)
Return = DoCmd(0xC, (Offset >> i) & 0x1, 0, NULL);
if (Return == NO_ERROR)
Return = DoCmd(0xC, 0, 0, NULL);
if (Return == NO_ERROR)
Return = DoCmd(0xA, 0, 0, NULL);
u16 BitBucket;
if (Return == NO_ERROR)
Return = DoCmd(9, 0, 0, NULL);
if (Return == NO_ERROR)
Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
if (Return == NO_ERROR)
Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
if (Return == NO_ERROR)
Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
if (Return == NO_ERROR)
Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
for (i = 0; (i < 0x40) && (Return == NO_ERROR); i++)
{
Return = DoCmd(0xB, 0, 0, (u8*)&BitBucket);
if (BitBucket & 0x0100)
dst[i >> 3] |= 1 << (7 - (i & 0x7));
}
if (Return == NO_ERROR)
Return = DoCmd(0xA, 0, 0, NULL);
return Return;
}
int AGP_LowReadEEprom(void* dst, u32 len, u32 offset) {
if (offset + len > 0x10000)
return BAD_PARAM;
if ((offset & 0x7) != 0x0)
return BAD_PARAM;
unsigned char* Buffer = dst;
u32 Offset;
u32 Return = NO_ERROR;
memset(Buffer, 0, len);
for (Offset = 0; (Offset < len) && (Return == NO_ERROR); Offset += 8)
Return = GetEEprom(Buffer + Offset, offset + Offset);
return Return;
}
int AGP_LowReadSRAM(void* dst, u32 len, u32 offset) {
if (offset + len > 0x10000)
return BAD_PARAM;
unsigned char* Buffer = dst;
u32 Offset = 0;
u32 Startoffset = offset;
for (; Offset < len; Offset++)
{
u32 RetVal = DoCmd(4, Offset + Startoffset, 0, Buffer + Offset);
if ((DoVerify) && (RetVal != NO_ERROR))
return RetVal;
}
return NO_ERROR;
}
EXI_DeviceAgp.cpp
Code: Select all
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include "Common/StdMakeUnique.h"
#include "Core/Core.h"
#include "Common/FileUtil.h"
#include "Common/MemoryUtil.h"
#include "Core/HW/EXI_Device.h"
#include "Core/HW/EXI_DeviceAgp.h"
#include "Core/ConfigManager.h"
void CEXIAgp::DoHash(u8* data, u32 size)
{
for (u32 it = 0; it < size; it++)
{
m_uHash = m_uHash ^ data[it];
m_uHash = m_pHashArray[m_uHash];
}
}
CEXIAgp::CEXIAgp() :
m_uPosition(0),
m_uAddress(0),
m_uRWOffset(0)
{
// Create the ROM
m_pHashArray = (u8*)AllocateMemoryPages(HASH_SIZE);
// Load whole ROM dump
std::string path;
std::string gbapath;
SplitPath(SConfig::GetInstance().m_LocalCoreStartupParameter.m_strBootROM, &path, nullptr, nullptr);
gbapath = path;
gbapath += "agp.gba";
LoadFileToROM(gbapath);
INFO_LOG(BOOT, "Loaded gba rom: %s", gbapath.c_str());
gbapath = path;
gbapath += "agp.sav";
LoadFileToROM(gbapath);
INFO_LOG(BOOT, "Loaded gba sav: %s", gbapath.c_str());
path += "agp.bin";
LoadFileToHash(path);
INFO_LOG(BOOT, "Loaded agp hash: %s", path.c_str());
WriteProtectMemory(m_pROM, ROM_SIZE);
WriteProtectMemory(m_pHashArray, HASH_SIZE);
m_uAddress = 0;
}
CEXIAgp::~CEXIAgp()
{
FreeMemoryPages(m_pROM, ROM_SIZE);
m_pROM = nullptr;
FreeMemoryPages(m_pHashArray, HASH_SIZE);
m_pHashArray = nullptr;
}
void CEXIAgp::DoState(PointerWrap &p)
{
p.Do(m_uPosition);
p.Do(m_uAddress);
p.Do(m_uRWOffset);
p.Do(m_uHash);
}
void CEXIAgp::LoadFileToROM(std::string filename)
{
File::IOFile pStream(filename, "rb");
if (pStream)
{
u64 filesize = pStream.GetSize();
ROM_SIZE = filesize & 0xFFFFFFFF;
ROM_MASK = (ROM_SIZE - 1);
m_pROM = (u8*)AllocateMemoryPages(ROM_SIZE);
pStream.ReadBytes(m_pROM, filesize);
}
}
void CEXIAgp::LoadFileToEEPROM(std::string filename)
{
File::IOFile pStream(filename, "rb");
if (pStream)
{
u64 filesize = pStream.GetSize();
EEPROM_SIZE = filesize & 0xFFFFFFFF;
EEPROM_MASK = (EEPROM_SIZE - 1);
m_pEEPROM = (u8*)AllocateMemoryPages(EEPROM_SIZE);
pStream.ReadBytes(m_pEEPROM, filesize);
}
}
void CEXIAgp::LoadFileToHash(std::string filename)
{
File::IOFile pStream(filename, "rb");
if (pStream)
{
u64 filesize = pStream.GetSize();
pStream.ReadBytes(m_pHashArray, filesize);
}
}
u32 CEXIAgp::ImmRead(u32 _uSize)
{
// We don't really care about _uSize
(void)_uSize;
u32 uData = 0;
u8 RomVal1, RomVal2, RomVal3,RomVal4;
switch (m_uCurrCmd)
{
case 0xAE000000:
uData = 0x5AAA5517; // 17 is precalculated hash
m_uCurrCmd = 0;
break;
case 0xAE010000:
uData = (m_uReturnPos == 0) ? 0x01020304 : 0xF0020304; // F0 is precalculated hash, 020304 is left over
if (m_uReturnPos == 1)
m_uCurrCmd = 0;
else
m_uReturnPos = 1;
break;
case 0xAE020000:
if (m_uRWOffset == 0x8000000)
{
RomVal1 = 0x0;
RomVal2 = 0x1;
}
else
{
RomVal1 = m_pROM[m_uRWOffset++];
RomVal2 = m_pROM[m_uRWOffset++];
}
DoHash(&RomVal2, 1);
DoHash(&RomVal1, 1);
uData = (RomVal2 << 24) | (RomVal1 << 16) | (m_uHash << 8);
m_uCurrCmd = 0;
break;
case 0xAE030000:
if (_uSize == 1)
{
uData = 0xFF000000;
m_uCurrCmd = 0;
}
else
{
RomVal1 = m_pROM[m_uRWOffset++];
RomVal2 = m_pROM[m_uRWOffset++];
RomVal3 = m_pROM[m_uRWOffset++];
RomVal4 = m_pROM[m_uRWOffset++];
DoHash(&RomVal2, 1);
DoHash(&RomVal1, 1);
DoHash(&RomVal4, 1);
DoHash(&RomVal3, 1);
uData = (RomVal2 << 24) | (RomVal1 << 16) | (RomVal4 << 8) | (RomVal3);
}
break;
case 0xAE0B0000:
RomVal1 = m_EEPROM_Pos < 4 ? 0xA : (((u64*)m_pEEPROM)[(m_EEPROM_Cmd >> 1) & 0x3F] >> (m_EEPROM_Pos - 4)) & 0x1;
RomVal2 = 0;
DoHash(&RomVal2, 1);
DoHash(&RomVal1, 1);
uData = (RomVal2 << 24) | (RomVal1 << 16) | (m_uHash << 8);
m_EEPROM_Pos++;
m_uCurrCmd = 0;
break;
case 0xAE0C0000:
uData = m_uHash << 24;
m_uCurrCmd = 0;
break;
default:
uData = 0x0;
m_uCurrCmd = 0;
break;
}
WARN_LOG(EXPANSIONINTERFACE, "AGP read %x", uData);
return uData;
}
void CEXIAgp::ImmWrite(u32 _uData, u32 _uSize)
{
if ((_uSize == 1) && ((_uData & 0xFF000000) == 0))
return;
u8 HashCmd;
u64 Mask;
WARN_LOG(EXPANSIONINTERFACE, "AGP command %x", _uData);
switch (m_uCurrCmd)
{
case 0xAE020000:
case 0xAE030000:
m_uRWOffset = ((_uData & 0xFFFFFF00) >> 7) & ROM_MASK;
m_uReturnPos = 0;
HashCmd = (_uData & 0xFF000000) >> 24;
DoHash(&HashCmd, 1);
HashCmd = (_uData & 0x00FF0000) >> 16;
DoHash(&HashCmd, 1);
HashCmd = (_uData & 0x0000FF00) >> 8;
DoHash(&HashCmd, 1);
break;
case 0xAE0C0000:
if ((m_EEPROM_Pos < 0x8) || (m_EEPROM_Pos == ((m_EEPROM_Cmd & EE_READ) ? 0x8 : 0x48)))
{
Mask = 1i64 << (0x8-(m_EEPROM_Pos > 0x8 ? 0x8 : m_EEPROM_Pos));
if ((_uData >> 16) & 0x1)
m_EEPROM_Cmd |= Mask;
else
m_EEPROM_Cmd &= ~Mask;
if (m_EEPROM_Pos == 0x48)
((u64*)(m_pEEPROM))[(m_EEPROM_Cmd >> 1) & 0x3F] = m_EEPROM_Data;
}
else
{
Mask = 1i64 << (0x47 - m_EEPROM_Pos);
if ((_uData >> 16) & 0x1)
m_EEPROM_Data |= Mask;
else
m_EEPROM_Data &= ~Mask;
}
m_EEPROM_Pos++;
m_uReturnPos = 0;
HashCmd = (_uData & 0xFF000000) >> 24;
DoHash(&HashCmd, 1);
HashCmd = (_uData & 0x00FF0000) >> 16;
DoHash(&HashCmd, 1);
break;
case 0xAE0B0000:
break;
case 0xAE000000:
case 0xAE010000:
case 0xAE090000:
case 0xAE0A0000:
default:
m_EEPROM_Pos = 0;
m_uCurrCmd = _uData;
m_uReturnPos = 0;
m_uHash = 0xFF;
HashCmd = (_uData & 0x00FF0000) >> 16;
DoHash(&HashCmd, 1);
break;
}
}
Code: Select all
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#pragma once
#include <deque>
#include <queue>
#include "Core/HW/EXI_Device.h"
class CEXIAgp
: public IEXIDevice
{
public:
CEXIAgp();
virtual ~CEXIAgp();
bool IsPresent() override { return true; }
void ImmWrite(u32 _uData, u32 _uSize) override;
u32 ImmRead(u32 _uSize) override;
void DoState(PointerWrap &p) override;
private:
enum
{
HASH_SIZE = 256,
HASH_MASK = (HASH_SIZE - 1),
EE_READ = 0x80
};
//! ROM
u32 ROM_SIZE;
u32 ROM_MASK;
u32 EEPROM_SIZE;
u32 EEPROM_MASK;
u8* m_pROM;
u8* m_pEEPROM;
u8* m_pHashArray;
//! Helper
u32 m_uPosition;
u32 m_uAddress;
u32 m_uRWOffset;
u64 m_EEPROM_Data;
u8 m_EEPROM_Pos;
u16 m_EEPROM_Cmd;
void LoadFileToROM(std::string filename);
void LoadFileToEEPROM(std::string filename);
void LoadFileToHash(std::string filename);
void DoHash(u8* data, u32 size);
u8 m_uHash;
u32 m_uCurrCmd;
u32 m_uReturnPos;
};
More complete information on the list of data to read from the discs for anyone who wants it:
For both Action Replay (v1.14A) and AGP:
Offset 0x0, Size 0x440 = Standard Disc header
In header: At Offset 0x424 = FST.bin location (0x17000), 0x428 = FSB.bin size (0x24)
In FST.bin (0x17000): At Offset 0x170010 = opening.bnr location (0x18000), 0x17014 = opening.bnr size = (0x1960)
Offset 0x2440, Size found at 0x2454 & 0x2458 (0xCCC + 0x192C0) = Apploader
Offset 0x2460, Size 0x19F8C = Apploader
All of the above can be read by just reading the first 0x100000 bytes from the disc,
(Offset 0, Size = 0x100000), which can be read without error.
After loading the apploader into memory, at address 0x812002E8 the main boot application is read (in memory 0x81200C20, which was originally from Offset 0x3080). There are two entries. Format:
(Memory destination, Size, Offset)
For both discs the values are
(0x80003100, 0xBA000 or 0x335000, 0x50000000)
(0x817FE8C0, 0x40, 0x17000)
The second set is already contained in the above (0, 0x100000).
The size of 0x335000 is for the Action Replay disc, and is the only other data read from the disc (I think). So the complete list of reads for AR 1.14A is
(0x00000000, 0x00100000)
(0x50000000, 0x00100000)
(0x50100000, 0x00100000)
(0x50200000, 0x00100000)
(0x50300000, 0x00100000)
The last one completes successfully, even if it doesn't need to read the full amount.
For the AGP (size 0xBA000) above, this is the main menu/appswitcher. We don't need the full amount, but it completes successfully if reading 0x100000. So far we have:
(0x00000000, 0x100000)
(0x50000000, 0x100000)
In the main menu code, at 0x80003420 is starts to load r25 with 0x8003B770 (Original Offset = 0x50038670). It finally completes the address loading at 0x80003864. This then reads from 0x8003B770 or 0x8003B778 one of the two formatted sets:
(Offset, Size>>10)
(0x12D00000, 0x800 [0x00200000]) = AGP
(0x12B00000, 0x800 [0x00200000]) = Cheat Construction Kit
These are both loaded to 0x80003100.
The Cheat Construction Kit doesn't load anything from the disc.
The AGP in the function located at 0x80004410 loads from 0x80098040 + 12 * r3 the GBA roms that are on the disc. So the list at 0x80098040 (Original Offset 0x12D94F40)
(FileName Address, Offset, Size>>10)
Bounty Hunter
(, 0x09900000, 0x400 [0x00100000])
Chopper 2
(, 0x0A900000, 0x400 [0x00100000])
Dragon Tiles 3
(, 0x0B100000, 0x400 [0x00100000])
Invaders
(, 0x0B900000, 0x400 [0x00100000])
Jetpack 2
(, 0x0C100000, 0x400 [0x00100000])
Loop The Loop
(, 0x0D900000, 0x400 [0x00100000])
Paddle Panic
(, 0x0E100000, 0x400 [0x00100000])
Popem
(, 0x0E900000, 0x400 [0x00100000])
Proxima
(, 0x0F900000, 0x400 [0x00100000])
Super Power Shot
(, 0x0F100000, 0x400 [0x00100000])
These are all exactly 0x100000 long, so we aren't reading past the end with 0x100000 size reads. There are further offsets/sizes in the list, so I was hoping there might be more hidden games, but the first is a repeat of one of the earlier ones with a different size, and I failed to read the one after that. I'm assuming these were left over from earlier debug builds of the disc, As there are only 10 games selectable in the game. The filenames also change from *.gba to NumberCharacters with no extension (e.g. "0000 SMBJR", "0001 AER" and "0002 TIH". So the complete list of offsets and sizes to read from the AGP disc:
(0x00000000, 0x00100000)
(0x50000000, 0x00100000)
(0x09900000, 0x00100000)
(0x0A900000, 0x00100000)
(0x0B100000, 0x00100000)
(0x0B900000, 0x00100000)
(0x0C100000, 0x00100000)
(0x0D900000, 0x00100000)
(0x0E100000, 0x00100000)
(0x0E900000, 0x00100000)
(0x0F900000, 0x00100000)
(0x0F100000, 0x00100000)