Datel AGP information

Portables, case replacements, mods etc, all in here!
Post Reply
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Datel AGP information

Post by GreyRogue » Mon Dec 15, 2014 4:58 am

I used 0s for everything except the listed address ranges (for checksum calculations - the disc doesn't appear to ever read outside these ranges).

(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):
Spoiler
Show

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;
}
Dolphin code (work in progress)
EXI_DeviceAgp.cpp
Spoiler
Show

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;
   }
}
EXI_DeviceAgp.h
Spoiler
Show

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;
};
Original post with disc offset source information.
Spoiler
Show
I thought I'd share my findings on the Datel AGP. Rather than hijack the EXI-IDE thread, I created a new one.

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)
Last edited by GreyRogue on Sat Jan 17, 2015 8:18 pm, edited 12 times in total.
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Mon Dec 15, 2014 5:09 am

I've also successfully dumped a cartridge without using the disc. I patched CleanRip because I'm lazy. The AGP must be unplugged when starting the executable (I've seen similar things if memory cards are plugged in when starting software). Once it's started, plug the AGP into Memory Card Slot B. Also, a disc must be in the drive (again because I was too lazy to fix it). This one currently dumps the first 4MiB. The AGP disc dumps the first 0x60 at 0x0, 0x200000, 0x400000, 0x800000 and compares them to determine the right size. This would be a reasonable feature to add. Also, there are 0x100 bytes from the disc that are used in a hash function to verify the values from the AGP. I wasn't sure whether or not to include them in the code. For now, the code reads a file call 12befa4c.bin (0x12befa4c is the address in the disc where the data is at). If this file is not found, the verify is skipped. For now, I'm probably done working on this if someone else wants to clean it up/improve it.
Spoiler
Show

Code: Select all

--- D:\Games\devKitPro\msys\home\cleanrip-read-only_orig\source\gc_agp.c
+++ D:\Games\devKitPro\msys\home\cleanrip-read-only\source\gc_agp.c
@@ -0,0 +1,204 @@
+/**
+ * CleanRip - gc_dvd.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 unsigned long* agp = (volatile unsigned long*) 0xCD806814;
+#else
+volatile unsigned long* agp = (volatile unsigned long*) 0xCC006814;
+#endif
+
+void WaitGC_Cycles(unsigned long Cycles)
+{
+	usleep((Cycles + 484) / 485); //round up
+}
+
+void WriteCmd(unsigned long Cmd, unsigned long Bytes)
+{
+	unsigned long WriteCode = 0x5;
+	unsigned long ByteCode = ((Bytes - 1) & 0x3) << 4;
+
+	agp[4] = Cmd;
+	agp[3] = ByteCode | WriteCode;
+	unsigned long Status = 1;
+	while (Status & 0x1)
+		Status = agp[3];
+}
+
+unsigned long ReadCmd(unsigned long Bytes)
+{
+	unsigned long ReadCode = 0x1;
+	unsigned long ByteCode = ((Bytes - 1) & 0x3) << 4;
+
+	agp[3] = ByteCode | ReadCode;
+	unsigned long Status = 1;
+	while (Status & 0x1)
+		Status = agp[3];
+	return agp[4];
+}
+
+bool DoVerify = false;
+u8 *HashArray = 0;
+int init_agp(u8 *HashArrayIn) {
+	HashArray = HashArrayIn;
+	if (HashArray[0xFF] != 0)
+		DoVerify = true;
+
+	unsigned long DevicePresent = 0x1000;
+	unsigned long DeviceInsertionInt = 0x800;
+	unsigned long Clock1 = 0x0;
+	unsigned long Clock8 = 0x30;
+	unsigned long ImmCmd0 = 0xAE000000;
+	unsigned long ImmCmd1 = 0xAE010000;
+	u32 StatusDev0 = 0x80;
+	u32 StatusIntAck = 0x405;
+
+	agp[0] = 0;
+	unsigned long Status = agp[0];
+	if ((Status & DevicePresent) == 0)
+		return -1;
+	WaitGC_Cycles(1000 * 0x6800);
+	Status = agp[0];
+	Status |= DeviceInsertionInt;
+	agp[0] = Status;
+	WaitGC_Cycles(1000 * 0x6800);
+
+	Status = agp[0];
+	Status = (Status & StatusIntAck) | StatusDev0 | Clock1;
+	agp[0] = Status;
+
+	WriteCmd(ImmCmd0, 2);
+	WriteCmd(0, 1);
+	unsigned long ReadData = ReadCmd(4);
+	if (ReadData != 0x5AAA5517)
+		return -2;
+
+	Status = agp[0];
+	Status = (Status & StatusIntAck);
+	agp[0] = Status;
+
+	Status = agp[0];
+	Status = (Status & StatusIntAck) | StatusDev0 | Clock8;
+	agp[0] = Status;
+
+	WriteCmd(ImmCmd1, 2);
+	WriteCmd(0, 1);
+	ReadData = ReadCmd(4);
+	if (ReadData != 0x01020304)
+		return -3;
+	ReadData = ReadCmd(1);
+	if ((ReadData & 0xFF000000) != 0xF0000000)
+		return -4;
+
+	Status = agp[0];
+	Status = (Status & StatusIntAck);
+	agp[0] = Status;
+
+	return 0;
+}
+
+u32 Verify(u32 SentData, u32 ReadData)
+{
+	u8 Hash1 = SentData >> 24;
+	u8 Hash2 = SentData >> 16;
+	u8 Hash3 = SentData >> 8;
+	u8 Hash4 = SentData >> 0;
+	u8 Hash5 = ReadData >> 24;
+	u8 Hash6 = ReadData >> 16;
+	u8 Hash7 = ReadData >> 8;
+	u8 Hash = Hash1 ^ 0xFF;
+	Hash = HashArray[Hash];
+	Hash = Hash ^ Hash2;
+	Hash = HashArray[Hash];
+	Hash = Hash ^ Hash3;
+	Hash = HashArray[Hash];
+	Hash = Hash ^ Hash4;
+	Hash = HashArray[Hash];
+	Hash = Hash ^ Hash5;
+	Hash = HashArray[Hash];
+	Hash = Hash ^ Hash6;
+	Hash = HashArray[Hash];
+	if (Hash != Hash7)
+		return -1;
+	return 0;
+}
+/*
+ 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 -1;
+	if ((offset & 0x1) != 0x0)
+		return -1;
+
+	unsigned char* Buffer = dst;
+	unsigned int BufferPos = 0;
+	unsigned long ImmCmd2 = 0xAE020000;
+	unsigned long Clock16 = 0x40;
+	u32 StatusDev0 = 0x80;
+	u32 StatusIntAck = 0x405;
+
+	u32 Offset;
+	u32 Startoffset = offset >> 1;
+	for (Offset = 0; Offset < (len >> 1); Offset++)
+	{
+		u32 Status = agp[0];
+		Status = (Status & StatusIntAck) | StatusDev0 | Clock16;
+		agp[0] = Status;
+
+		WriteCmd(ImmCmd2, 2);
+		WriteCmd(0, 1);
+		u32 OffsetVal = Offset + Startoffset;
+		WriteCmd(((OffsetVal) << 8), 3);
+		WaitGC_Cycles(475 * 2);
+		WriteCmd(0, 1);
+		u32 ReadData = ReadCmd(3);
+		if ((DoVerify) && (0 != Verify(0x02000000 | (OffsetVal), ReadData)))
+			return -1;
+		Buffer[BufferPos++] = (ReadData >> 16) & 0xFF;
+		Buffer[BufferPos++] = (ReadData >> 24) & 0xFF;
+
+		Status = agp[0];
+		Status = (Status & StatusIntAck);
+		agp[0] = Status;
+	}
+	
+	return 0;
+}
+
+
--- D:\Games\devKitPro\msys\home\cleanrip-read-only_orig\include\gc_agp.h
+++ D:\Games\devKitPro\msys\home\cleanrip-read-only\include\gc_agp.h
@@ -0,0 +1,41 @@
+/**
+ * CleanRip - gc_agp.h (originally from Cube64/Wii64)
+ * Copyright (C) 2010 emu_kidid
+ *
+ * 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.
+ *
+ **/
+
+#ifndef GC_AGP_H
+#define GC_AGP_H
+
+#include <stdint.h>
+//Used in ISO9660 Parsing
+#define NO_FILES -1
+#define NO_ISO9660_DISC -2
+#define FATAL_ERROR -3
+#define MAXIMUM_ENTRIES_PER_DIR 512
+
+#define GC_CPU_VERSION 0x00083214
+#define NO_HW_ACCESS -1000
+#define NO_DISC      -1001
+#define NORMAL 0xA8000000
+#define DVDR   0xD0000000
+
+int init_agp();
+int AGP_LowRead64(void* dst, unsigned int len, uint64_t offset);
+
+#endif
+
--- D:\Games\devKitPro\msys\home\cleanrip-read-only_orig\source\main.c
+++ D:\Games\devKitPro\msys\home\cleanrip-read-only\source\main.c
@@ -33,6 +33,7 @@
 #include <ogc/machine/processor.h>
 #include "FrameBufferMagic.h"
 #include "IPLFontWrite.h"
+#include "gc_agp.h"
 #include "gc_dvd.h"
 #include "verify.h"
 #include "main.h"
@@ -974,12 +975,15 @@
 	int silent = options_map[WII_NEWFILE];
 
 	// The read size
-	int opt_read_size = READ_SIZE;
+	int opt_read_size = 0x200;
+//	int opt_read_size = READ_SIZE;
 
 	u32 startLBA = 0;
-	u32 endLBA = disc_type == IS_NGC_DISC ? NGC_DISC_SIZE
-			: (options_map[WII_DUAL_LAYER] == DUAL_LAYER ? WII_D9_SIZE
-					: WII_D5_SIZE);
+	u32 endLBA = 0x400000;
+	//	u32 endLBA = disc_type == IS_NGC_DISC ? NGC_DISC_SIZE
+//			: (options_map[WII_DUAL_LAYER] == DUAL_LAYER ? WII_D9_SIZE
+//					: WII_D5_SIZE);
+
 
 	// Work out the chunk size
 	u32 chunk_size_wii = options_map[WII_CHUNK_SIZE];
@@ -998,6 +1002,7 @@
 	if (disc_type == IS_NGC_DISC) {
 		opt_chunk_size = NGC_DISC_SIZE;
 	}
+	opt_chunk_size = 0x1000000;
 
 	// Dump the BCA for Nintendo discs
 #ifdef HW_RVL
@@ -1018,6 +1023,30 @@
 		SHA1Reset(&sha);
 		crc32 = 0;
 	}
+	u8 HashArray[0x100];
+	sprintf(txtbuffer, "%s12befa4c.bin", &mountPath[0]);
+	FILE *fp = fopen(txtbuffer, "rb");
+	if (fp)
+	{
+		fread(HashArray, 1, 0x100, fp);
+		fclose(fp);
+		DrawFrameStart();
+		DrawEmptyBox(30, 180, vmode->fbWidth - 38, 350, COLOR_BLACK);
+		WriteCentre(230, "Read file:");
+		WriteCentre(255, txtbuffer);
+		DrawFrameFinish();
+		sleep(5);
+	}
+	else
+	{
+		HashArray[0xFF] = 0;
+		DrawFrameStart();
+		DrawEmptyBox(30, 180, vmode->fbWidth - 38, 350, COLOR_BLACK);
+		WriteCentre(230, "Skipping verify, Failed to read file:");
+		WriteCentre(255, txtbuffer);
+		DrawFrameFinish();
+		sleep(5);
+	}
 
 	// There will be chunks, name accordingly
 	if (opt_chunk_size < endLBA) {
@@ -1026,7 +1055,7 @@
 		sprintf(txtbuffer, "%s%s.iso", &mountPath[0], &gameName[0]);
 	}
 	remove(&txtbuffer[0]);
-	FILE *fp = fopen(&txtbuffer[0], "wb");
+	fp = fopen(&txtbuffer[0], "wb");
 	if (fp == NULL) {
 		DrawFrameStart();
 		DrawEmptyBox(30, 180, vmode->fbWidth - 38, 350, COLOR_BLACK);
@@ -1046,7 +1075,21 @@
 	u64 startTime = gettime();
 	int chunk = 1;
 
-	while (!ret && (startLBA + (opt_read_size>>11)) < endLBA) {
+	int agpres = init_agp(HashArray);
+	if (agpres != 0) {
+		DrawFrameStart();
+		DrawEmptyBox(30, 180, vmode->fbWidth - 38, 350, COLOR_BLACK);
+		WriteCentre(230, "Failed to init agp:");
+		sprintf(txtbuffer, "0x%X", agpres);
+		WriteCentre(255, txtbuffer);
+		WriteCentre(315, "Exiting in 5 seconds");
+		DrawFrameFinish();
+		sleep(5);
+		exit(0);
+	}
+
+//	while (!ret && (startLBA + (opt_read_size>>11)) < endLBA) {
+	while (!ret && (startLBA + (opt_read_size)) < endLBA) {
 		MQ_Receive(blockq, (mqmsg_t*)&wmsg, MQ_MSG_BLOCK);
 		if (wmsg==NULL) { // asynchronous write error
 			LWP_JoinThread(writer, NULL);
@@ -1090,7 +1133,8 @@
 		wmsg->ret_box = blockq;
 
 		// Read from Disc
-		ret = DVD_LowRead64(wmsg->data, (u32)opt_read_size, (u64)startLBA << 11);
+//		ret = DVD_LowRead64(wmsg->data, (u32)opt_read_size, (u64)startLBA << 11);
+		ret = AGP_LowRead64(wmsg->data, (u32)opt_read_size, (u64)startLBA);
 		MQ_Send(msgq, (mqmsg_t)wmsg, MQ_MSG_BLOCK);
 		if(calcChecksums) {
 			// Calculate MD5
@@ -1112,8 +1156,10 @@
 		int timePassed = diff_msec(copyTime, curTime);
 		if (timePassed >= 1000) {
 			u64 totalTime = diff_msec(startTime, curTime);
-			u32 bytes_per_msec = (((u64)startLBA<<11) + opt_read_size) / totalTime;
-			u64 remainder = (((u64)endLBA - startLBA)<<11) - opt_read_size;
+//			u32 bytes_per_msec = (((u64)startLBA<<11) + opt_read_size) / totalTime;
+//			u64 remainder = (((u64)endLBA - startLBA)<<11) - opt_read_size;
+			u32 bytes_per_msec = (((u64)startLBA) + opt_read_size) / totalTime;
+			u64 remainder = (((u64)endLBA - startLBA)) - opt_read_size;
 
 			u32 etaTime;
 			if(disc_type == IS_NGC_DISC) {
@@ -1124,7 +1170,8 @@
 				etaTime = (remainder / bytes_per_msec) / 1000;
 			}
 			sprintf(txtbuffer, "%dMB %4.0fKB/s - ETA %02d:%02d:%02d",
-					(int) (((u64) ((u64) startLBA << 11)) / (1024* 1024 )),
+//					(int) (((u64) ((u64) startLBA << 11)) / (1024* 1024 )),
+					(int) (((u64) ((u64) startLBA)) / (1024* 1024 )),
 				(float)bytes_per_msec*1000/1024,
 				(int)((etaTime/60/60)%60),(int)((etaTime/60)%60),(int)(etaTime%60));
 			DrawFrameStart();
@@ -1132,7 +1179,8 @@
       		DrawFrameFinish();
   			copyTime = curTime;
 		}
-		startLBA+=opt_read_size>>11;
+//		startLBA+=opt_read_size>>11;
+		startLBA+=opt_read_size;
 	}
 	// Remainder of data
 	if(!ret && startLBA < endLBA) {
@@ -1150,10 +1198,12 @@
 		}
 		wmsg->command =  MSG_WRITE;
 		wmsg->data = wmsg+1;
-		wmsg->length = (u32)((endLBA-startLBA)<<11);
+//		wmsg->length = (u32)((endLBA-startLBA)<<11);
+		wmsg->length = (u32)((endLBA-startLBA));
 		wmsg->ret_box = blockq;
 
-		ret = DVD_LowRead64(wmsg->data, wmsg->length, (u64)startLBA << 11);
+//		ret = DVD_LowRead64(wmsg->data, wmsg->length, (u64)startLBA << 11);
+		ret = AGP_LowRead64(wmsg->data, wmsg->length, (u64)startLBA);
 		MQ_Send(msgq, (mqmsg_t)wmsg, MQ_MSG_BLOCK);
 		if(calcChecksums) {
 			// Calculate MD5
@@ -1180,7 +1230,8 @@
 	if(ret != -61 && ret) {
 		DrawFrameStart();
 		DrawEmptyBox (30,180, vmode->fbWidth-38, 350, COLOR_BLACK);
-		sprintf(txtbuffer, "%s",dvd_error_str());
+//		sprintf(txtbuffer, "%s",dvd_error_str());
+		sprintf(txtbuffer, "%08x", startLBA);
 		WriteCentre(255,txtbuffer);
 		WriteCentre(315,"Press  A  to continue");
 		dvd_motor_off();
Last edited by GreyRogue on Mon Dec 22, 2014 6:08 am, edited 1 time in total.
User avatar
emu_kidid
Site Admin
Posts: 4927
Joined: Mon Mar 29, 2010 10:06 am
Location: Australia
Contact:

Re: Datel AGP information

Post by emu_kidid » Mon Dec 15, 2014 6:00 am

Nice! I might mirror some of this onto the Wiki if you don't get to it first.
Image
User avatar
MockyLock
Posts: 330
Joined: Tue Aug 07, 2012 8:12 pm
Location: France

Re: Datel AGP information

Post by MockyLock » Mon Dec 15, 2014 6:11 am

Well, i don't understand anything of your description, as i'm not coder/dev at all.
Anyway, I understood that the AGP disc is finally dumped :]
Unless a "Dumping AGP disc for the noob" guide wil ba available, i hope a a functionnal dump will be available one day.
Thank you very much for your work !
vinzanity
Posts: 24
Joined: Tue Jun 04, 2013 5:35 am

Re: Datel AGP information

Post by vinzanity » Mon Dec 15, 2014 10:56 am

By any chance, for us who do not know how to patch cleanrip, will there be a special build of cleanrip so that we could dump our own disc? Thanks.
User avatar
emu_kidid
Site Admin
Posts: 4927
Joined: Mon Mar 29, 2010 10:06 am
Location: Australia
Contact:

Re: Datel AGP information

Post by emu_kidid » Mon Dec 15, 2014 11:47 am

I will write a dumper shortly if no one else bothers.
Image
User avatar
MockyLock
Posts: 330
Joined: Tue Aug 07, 2012 8:12 pm
Location: France

Re: Datel AGP information

Post by MockyLock » Mon Dec 15, 2014 12:11 pm

But, correct me if i'm wrong, we speak about 2 dumps :
- dumping the AGP disc itself (and i hope, in order to boot it with Swiss)
- dumping GBA cartridge through the AGP slot

GreyRogue wrote its dumper for the GBA cartridge, right ?
User avatar
emu_kidid
Site Admin
Posts: 4927
Joined: Mon Mar 29, 2010 10:06 am
Location: Australia
Contact:

Re: Datel AGP information

Post by emu_kidid » Mon Dec 15, 2014 12:25 pm

Yes, and information on about the disc too. This would be a 2-in-1 software tied specifically to dumping the disc or dumping a GBA cart/save.
Image
User avatar
MockyLock
Posts: 330
Joined: Tue Aug 07, 2012 8:12 pm
Location: France

Re: Datel AGP information

Post by MockyLock » Mon Dec 15, 2014 12:44 pm

Good news !! My lone AGP slot may become useful soon :)
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Mon Dec 15, 2014 11:49 pm

A few more notes:
0x8009301C (Original Disc Offset = 0x12D8FF1C): Location of the different commands:
0xAE00 - Init? (No parameters; returns 0x5AAA55)
0xAE01 - Init? (No parameters; returns 0x01020304 (always?))
0xAE02 - Read 16 bit value from address in parameter 1.
0xAE03
0xAE04
0xAE07
0xAE09
0xAE0A
0xAE0B
0xAE0C

I've only messed with the first 3 so far. Possibilities for some of the others: DMA, Save manipulation, Cartridge eject, etc.

The values being passed for the first two functions have the same verify routine as the 0xAE02 command. The routine hashes the outgoing command followed by the incoming values and compares with the last byte received. For example the AE02 command ignores the AE and hashes 02XXXXXXYYYY and compares with last (third) byte received, where the Xs are the offset to read and the Ys are the first two bytes received (the third is the hash compare value).
For the AE01, there are no parameters, so the hash is performed on 01YYYYYYYY, where the Ys are the first four bytes returned and the fifth is the compare value. The return value for AE00 is always 0x5AAA55 (with a hash of 0x17). I'm uncertain if the return for AE01 is always 0x01020304 (with hash 0xF0). The code I was looking at checked the hash, but threw away the return value for AE01. It specifically checked AE00 for 5AAA55.

The AGP can, of course, be moved. Hardware registers can be moved from 0xCD806814-24 to 0xCD806800-10 and using memory card slot A instead of B. This avoids the issue Devkit/libogc has with things plugged into slot B at launch.

It looks like most games have a title string at 0xA0. This might be a good place to grab a name to autoname the gba file. At least some of the gba files on the disc have null strings there, so a fallback naming scheme would probably still be needed in some cases.

If there is no cartridge in the AGP, it reads the address requested (non-byte-swapped, unlike successful reads) for all addresses in response to AE02 (e.g. 0000001000020003). The disc also checks for this when reading the cartridge.
User avatar
emu_kidid
Site Admin
Posts: 4927
Joined: Mon Mar 29, 2010 10:06 am
Location: Australia
Contact:

Re: Datel AGP information

Post by emu_kidid » Mon Dec 15, 2014 11:54 pm

I can't believe they bothered to hash things :(

Thanks for the additional info :)
Image
vinzanity
Posts: 24
Joined: Tue Jun 04, 2013 5:35 am

Re: Datel AGP information

Post by vinzanity » Tue Dec 16, 2014 4:33 am

emu_kidid wrote:I will write a dumper shortly if no one else bothers.
Yehey! My AGP disk has a crack at the center and I've been looking for a way to have a backup of it in case it fails. Thanks guys :D . AGP is the only way to play GBA games on the Wii (besides emulation).
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Sat Dec 20, 2014 1:15 am

Yeah. The hash is weird, because you don't need it. You can just ignore it. It looks like they're using it as a parity check, so I don't know why they're using the strange hash to do it.

Any way, I mostly figured out another of the commands (AE03). It's a DMA. It works very similarly to AE02. You specify an offset. Unlike the AE02, though, you then read back 0x20000 bytes without having to specify more addresses (it's quite a bit faster). Also, it reads one byte at the end which the disc ignores. I thought this was the hash value, but it doesn't seem to be. The game uses AE02 to determine the ROM size, then uses AE03 to actually do the entire ROM read. AE03 mostly works in my replacement gc_agp.c file below, but it sometimes inserts an extra 0000. I tried reading 3 bytes instead of 1 at the end of each command, and then I may not have been getting the extra 0s, but some of the data was still occasionally randomly wrong. Whatever's wrong with this would need to be fixed before using AE03. Since AE02 works, I probably won't spend any more time trying to figure it out.
Spoiler
Show

Code: Select all

/**
 * CleanRip - gc_dvd.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 unsigned long* agp = (volatile unsigned long*) 0xCD806800;
#else
volatile unsigned long* agp = (volatile unsigned long*) 0xCC006800;
#endif

void WaitGC_Cycles(unsigned long Cycles)
{
	usleep((Cycles + 484) / 485); //round up
}


void UpdateStatus(u32 StatusAdd)
{
	u32 StatusIntAck = 0x405;
	u32 Status = agp[0];
	Status = (Status & StatusIntAck) | StatusAdd;
	agp[0] = Status;
}

void WriteCmd(unsigned long Cmd, unsigned long Bytes)
{
	unsigned long WriteCode = 0x5;
	unsigned long ByteCode = ((Bytes - 1) & 0x3) << 4;

	agp[4] = Cmd;
	agp[3] = ByteCode | WriteCode;
	unsigned long Status = 1;
	while (Status & 0x1)
		Status = agp[3];
}

unsigned long ReadCmd(unsigned long Bytes)
{
	unsigned long ReadCode = 0x1;
	unsigned long ByteCode = ((Bytes - 1) & 0x3) << 4;

	agp[3] = ByteCode | ReadCode;
	unsigned long Status = 1;
	while (Status & 0x1)
		Status = agp[3];
	return agp[4];
}

bool DoVerify = false;
u8 *HashArray = 0;
int init_agp(u8 *HashArrayIn) {
	HashArray = HashArrayIn;
	if (HashArray[0xFF] != 0)
		DoVerify = true;

	unsigned long DevicePresent = 0x1000;
	unsigned long DeviceInsertionInt = 0x800;
	unsigned long Clock1 = 0x0;
	unsigned long Clock8 = 0x30;
	unsigned long ImmCmd0 = 0xAE000000;
	unsigned long ImmCmd1 = 0xAE010000;
	u32 StatusDev0 = 0x80;

	agp[0] = 0;
	unsigned long Status = agp[0];
	if ((Status & DevicePresent) == 0)
		return -1;
	WaitGC_Cycles(1000 * 0x6800);
	Status = agp[0];
	Status |= DeviceInsertionInt;
	agp[0] = Status;
	WaitGC_Cycles(1000 * 0x6800);

	UpdateStatus(StatusDev0 | Clock1);

	WriteCmd(ImmCmd0, 2);
	WriteCmd(0, 1);
	unsigned long ReadData = ReadCmd(4);
	if (ReadData != 0x5AAA5517)
		return -2;

	UpdateStatus(0);

	UpdateStatus(StatusDev0 | Clock8);

	WriteCmd(ImmCmd1, 2);
	WriteCmd(0, 1);
	ReadData = ReadCmd(4);
	if (ReadData != 0x01020304)
		return -3;
	ReadData = ReadCmd(1);
	if ((ReadData & 0xFF000000) != 0xF0000000)
		return -4;

	UpdateStatus(0);

	return 0;
}

u8 DoHash(u8 Initial, u8* Data, u32 Length)
{
	u32 Index;
	u8 Hash = Initial;
	for (Index = 0; Index < Length; Index++)
	{
		Hash = Hash ^ Data[Index];
		Hash = HashArray[Hash];
	}
	return Hash;
}
/*
 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 -1;
	if ((offset & 0x1) != 0x0)
		return -1;

	unsigned char* Buffer = dst;
	u32 BufferPos = 0;
	u32 ImmCmd2 = 0xAE020000;
	u32 ImmCmd3 = 0xAE030000;
	u32 Clock16 = 0x40;
	u32 StatusDev0 = 0x80;
	u32 InitialHash = 0xFF;

	u32 Offset = 0;
	u32 Startoffset = offset >> 1;

	for (; Offset + 0x10000 <= (len >> 1); Offset += 0x10000)
	{
		UpdateStatus(StatusDev0 | Clock16);

		WriteCmd(ImmCmd3, 2);
		WriteCmd(0, 1);
		u32 OffsetVal = (Offset + Startoffset) << 8;
		WriteCmd((OffsetVal), 3);
		WriteCmd(0, 1);

		u8 HashVal = 0;
		if (DoVerify)
		{
			HashVal = DoHash(InitialHash, (u8*)(&ImmCmd3) + 1, 1); //skip 0xAE
			HashVal = DoHash(HashVal, (u8*)(&OffsetVal), 3);
		}
		u32 BufferOffset;
		for (BufferOffset = 0; BufferOffset < 0x8000; BufferOffset++)
		{
			u32 ReadData = ReadCmd(4);
			Buffer[BufferPos++] = (ReadData >> 16) & 0xFF;
			Buffer[BufferPos++] = (ReadData >> 24) & 0xFF;
			Buffer[BufferPos++] = (ReadData >> 0) & 0xFF;
			Buffer[BufferPos++] = (ReadData >> 8) & 0xFF;
			HashVal = DoHash(HashVal, (u8*)&ReadData, 4);
		}
		u32 ReadData = ReadCmd(1);
		if (DoVerify)
		{
			if (HashVal != ((ReadData >> 24) & 0xFF))
				return -1;
		}
		UpdateStatus(0);
	}

	for (; Offset < (len >> 1); Offset++)
	{
		UpdateStatus(StatusDev0 | Clock16);

		WriteCmd(ImmCmd2, 2);
		WriteCmd(0, 1);
		u32 OffsetVal = (Offset + Startoffset) << 8;
		WriteCmd((OffsetVal), 3);
		WaitGC_Cycles(475 * 2);
		WriteCmd(0, 1);
		u32 ReadData = ReadCmd(3);
		if (DoVerify)
		{
			u8 HashVal = DoHash(InitialHash, (u8*)(&ImmCmd2) + 1, 1); //skip 0xAE
			HashVal = DoHash(HashVal, (u8*)(&OffsetVal), 3);
			HashVal = DoHash(HashVal, (u8*)&ReadData, 2);
			if (HashVal != ((ReadData >> 8) & 0xFF))
				return -1;
		}
		Buffer[BufferPos++] = (ReadData >> 16) & 0xFF;
		Buffer[BufferPos++] = (ReadData >> 24) & 0xFF;

		UpdateStatus(0);
	}
	
	return 0;
}


Last edited by GreyRogue on Tue Dec 23, 2014 3:51 pm, edited 2 times in total.
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Sat Dec 20, 2014 5:02 am

I've gone through the commands I could find in the code. I even found another one (0xAE06) that wasn't in the list of commands, but is hard-coded in the code. I don't know what they do, but this is how the disc uses them:
Spoiler
Show

Code: Select all

Hash is calculated one byte at a time.  Start with 0xFF, then apply command (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.

Before commands, ensure device present
Disc calls AE00, then AE01.  Then calls AE02 0x60 times each at 0x0, 0x100000, 0x200000... etc. to find the size (repeats or returns 0000, 0001, 0002,... not byte swapped)
Once it has the size, it calls AE03 repeatedly to read the ROM.

1MHz = 0, 8MHz = 0x30, 16MHz = 0x40
StatusEnable(ClockCode)  => *(0xCC006814) = 0x80 | ClockCode
StatusDisable            => *(0xCC006814) = 0x0

WriteImm(Data, Bytes)    => *(0xCC006824) = (Data << ((4-Bytes)*8))
                         => *(0xCC006820) = 5 | ((Bytes - 1)>>4)
                         => Wait for *(0xCC006820) & 0x1 == 0

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 16bit values at Offset
	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.
	StatusDisable()

AE04 = ???? (retry up to 4 times)
	StatusEnable(16MHz)
	WriteImm(0xAE04, 2)
	WriteImm(0x00, 1)
	WriteImm(0xXXXX, 2): 0xXXXX=???
	Wait(475 cycles)
	WriteImm(0x00, 1)
	ReadImm(0xYYZZ, 2): 0xYY=????, 0xZZ=Hash
	StatusDisable()

AE06 = ????
	StatusEnable(16MHz)
	WriteImm(0xAE06, 2)
	WriteImm(0x00, 1)
	WriteImm(0xXXXXXX, 3): 0xXXXXXX=???
	WriteImm(0xYYYY, 2): 0xYYYY=???
	Wait(475 cycles)
	WriteImm(0x00, 1)
	ReadImm(0xZZ, 1): 0xZZ=Hash
	StatusDisable()

AE07 = ???? (retry up to 4 times)
	StatusEnable(16MHz)
	WriteImm(0xAE07, 2)
	WriteImm(0x00, 1)
	WriteImm(0xXXXXYY, 3): 0xXXXX=???,  0xYY=???
	Wait(475 cycles)
	WriteImm(0x00, 1)
	ReadImm(0xZZ, 1): 0xZZ=Hash
	StatusDisable()

AE09 = ????
	StatusEnable(16MHz)
	WriteImm(0xAE09, 2)
	StatusDisable()

AE0A = ????
	StatusEnable(16MHz)
	WriteImm(0xAE0A, 2)
	StatusDisable()

AE0B = ???? (retry up to 4 times)
	StatusEnable(16MHz)
	WriteImm(0xAE0B, 2)
	Wait(475 cycles)
	Wait(475 cycles)
	WriteImm(0x00, 1)
	ReadImm(0xXXXXYY, 3): 0xXXXX=???, 0xYY=Hash
	StatusDisable()

AE0C = ???? (retry up to 4 times)
	StatusEnable(16MHz)
	WriteImm(0xAE0C, 2)
	WriteImm(0x00, 1)
	WriteImm(0xXXXX, 2): 0xXXXX=???
	Wait(475 cycles)
	WriteImm(0x00, 1)
	ReadImm(0xYY, 1): 0xYY=Hash
	StatusDisable()
Last edited by GreyRogue on Mon Dec 22, 2014 6:06 am, edited 2 times in total.
tueidj
Posts: 564
Joined: Fri May 03, 2013 6:57 am

Re: Datel AGP information

Post by tueidj » Sat Dec 20, 2014 5:36 am

It's safe to combine writes and reads (up to 4 bytes at a time) and use DMA reads instead of multiple immediate reads, from the point of view of the device in the EXI slot there is no difference.
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Mon Dec 22, 2014 1:48 am

Good to know about the EXI combining. I'll probably leave them like I have them for now so it's easier to compare to the code. That should make it easier to tell if I made a mistake.

More information.
I got AE03 to work. Based on how all the other commands work, I added a WaitGC_Cycles(475) between setting the offset and the WriteCmd(0, 1). This made it work more consistently. I still prefer the AE02 command because it has the hash check. It is a little slower (couple seconds for 4MB AE03, 10s of seconds for AE02). The extra read for AE03 does always appear to be 0xFF (not the hash). Also, AE03 is self-contained auto-increment read with a pre-determined length. This is different than the DMAs below.

I think I've determined the following commands:
AE09 = Start DMA
AE0C = Write DMA value
AE0B = Read DMA value
AE0A = End DMA

So, after the disc finishes loading the ROM with AE03(), it does this:
AE09()
AE0C() 9 times with [1,1,0,1,0,1,0,0,0]
AE0A()
AE09()
AE0B() 68 times, returning values where the 0x0100 bit changes
AE0A()
AE09()
AE0C() 73 times with values where the 0x1 bit changes
AE0A()

It then repeatedly calls
AE02(0x800000)
AE0A()
looking for the returned data from AE02 to be 0x0100 (I think).

The number of times things are called matches the EEPROM serial interface for GBA saves. I'm currently testing with Frogger, which I believe has a 512 byte EEPROM. See http://problemkaputt.de/gbatek.htm#gbacartbackupeeprom. Using 0x800000 instead of 0xDFFFF00 might be specific to this game or a difference with the AGP.
I'm worried timing is important with these. As these are being called by the emulated games, the timing is much harder to determine.
If this is correct, it stands to reason that AE04 is 8-bit read and AE07 is 8-bit write used for SRAM and FlashROM.

It's getting harder to debug, because I can't get Dolphin to get that far. For those playing along at home, I got it to work in Dolphin by using IPL (needed for interrupt code [0x80000500, 0x80000C00, etc.]?) Then, whenever I get a FIFO error, I save the state, restart Dolphin and load the state. Using this I can get all the way to after it loads the GBA ROM, to where it starts running the GBA. I'm getting panic errors now, and I don't know if they should be there or not. Does anyone know of a PC GBA debbugger (like Dolphin for Gamecube)?

Edits to Dolphin (full of hacks and ugly code):
EXI_DeviceAgp.cpp
Spoiler
Show

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_pROM = (u8*)AllocateMemoryPages(ROM_SIZE);
	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";
	path += "agp.bin";
	LoadFileToROM(gbapath);
	INFO_LOG(BOOT, "Loaded gba rom: %s", gbapath.c_str());
	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();

		pStream.ReadBytes(m_pROM, 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:
		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;
	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;
	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 0xAE000000:
	case 0xAE010000:
	default:
		m_uCurrCmd = _uData;
		m_uReturnPos = 0;
		m_uHash = 0xFF;
		HashCmd = (_uData & 0x00FF0000) >> 16;
		DoHash(&HashCmd, 1);
		break;
	}
}
EXI_DeviceAgp.h
Spoiler
Show

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
	{
		ROM_SIZE = 1024 * 1024 * 4,
		ROM_MASK = (ROM_SIZE - 1),
		HASH_SIZE = 256,
		HASH_MASK = (HASH_SIZE - 1)
	};

	//! ROM
	u8* m_pROM;
	u8* m_pHashArray;

	//! Helper
	u32 m_uPosition;
	u32 m_uAddress;
	u32 m_uRWOffset;

	void LoadFileToROM(std::string filename);
	void LoadFileToHash(std::string filename);
	void DoHash(u8* data, u32 size);
	u8 m_uHash;

	u32 m_uCurrCmd;
	u32 m_uReturnPos;
};
Plus some minor edits to add it as a choice to the Memory Card slots.
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Mon Dec 22, 2014 6:05 am

Confirmed AE04. Successfully dumped my SRAM save data from Mario Golf. With the dump from the ROM, fired it up in VBA and my save worked fine.
I wasn't brave enough to try the overwrite with AE07.

Updated gc_agp.c. EEprom is a work in progress. Doesn't work yet.
Spoiler
Show

Code: Select all

/**
 * CleanRip - gc_dvd.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];
}

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 << 1 | 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;
			}
			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;
			}
			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 = DoCmd(9, 0, 0, NULL);
	Return = DoCmd(0xC, 1, 0, NULL);
	Return = DoCmd(0xC, 1, 0, NULL);
	for (i = 11; i > 5; i--)
		Return = DoCmd(0xC, (Offset >> i) & 0x1, 0, NULL);
	Return = DoCmd(0xC, 0, 0, NULL);
	Return = DoCmd(0xA, 0, 0, NULL);

	u32 BitBucket;
	Return = DoCmd(9, 0, 0, NULL);
	Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
	Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
	Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
	Return = DoCmd(0xB, 1, 0, (u8*)&BitBucket);
	for (i = 0; i < 0x40; i++)
		Return = DoCmd(0xB, 0, 0, dst + (i * 4));
	Return = DoCmd(0xA, 0, 0, NULL);
	return Return;
}

int AGP_LowReadEEprom64(void* dst, unsigned int len, uint64_t offset) {
	if (offset + len > 0x10000)
		return BAD_PARAM;
	if ((offset & 0x7) != 0x0)
		return BAD_PARAM;

	unsigned char* Buffer = dst;
	return GetEEprom(Buffer, 0);
}

int AGP_LowRead64SRAM(void* dst, unsigned int len, uint64_t offset) {
	if (offset + len > 0x2000000)
		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;
}
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Tue Dec 23, 2014 8:33 am

Confirmed AE09, AE0A, AE0B, and AE0C. Successfully dumped my EEPROM save files. I had to add timing delays to make it work. I'm not sure what the requirements are, but adding an extra WaitGC_Cycles(475) [=1us] after each WriteCmd got it working. Cmd summary:

Hash is calculated one byte at a time. Start with 0xFF, then apply command (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.

Before commands, ensure device present
Disc calls AE00, then AE01. Then calls AE02 0x60 times each at 0x0, 0x100000, 0x200000... etc. to find the size (repeats or returns 0000, 0001, 0002,... not 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

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.
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 ??)
StatusEnable(16MHz)
WriteImm(0xAE06, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXXXX, 3): 0xXXXXXX=???
WriteImm(0xYYYY, 2): 0xYYYY=???
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) (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE0B, 2)
Wait(475 cycles)
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xXXXXYY, 3): 0xXXXX=???, 0xYY=Hash
StatusDisable()

AE0C = Write DMA (used for EEPROM - only 0x0100 bit) (retry up to 4 times)
StatusEnable(16MHz)
WriteImm(0xAE0C, 2)
WriteImm(0x00, 1)
WriteImm(0xXXXX, 2): 0xXXXX=???
Wait(475 cycles)
WriteImm(0x00, 1)
ReadImm(0xYY, 1): 0xYY=Hash
StatusDisable()

gc_agp.cpp
Spoiler
Show

Code: Select all

/**
 * CleanRip - gc_dvd.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 << 1 | 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;
}
I also figured out my Dolphin issue. I figured out how to use the debugger in VBA enough to know that the panic errors I was getting were being caused by the overflow processing not being implemented for addco. and subfco. instructions. I commented out the errors and ran well enough to make it to EEPROM processing.

Here's some updated Dolphin code for running with AGP device.
EXI_DeviceAgp.cpp
Spoiler
Show

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;
	}
}
EXI_DeviceAgp.h
Spoiler
Show

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;
};
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Mon Jan 05, 2015 2:14 am

I just noticed that these two videos show different versions of the AGP disc. The one I have has the construction kit and looks like this:
https://www.youtube.com/watch?v=WCc5EHj8_bw
(skip ahead to 1:15 to see the first menu). He doesn't show the built in games (press left and right when prompted to insert cartridge and press A), but I assume they're there.

This must be another version:
https://www.youtube.com/watch?v=YlU5gP5pUxs
Skip to 2:25 to see there is no Cheat Construction Kit selection. The list of disc offsets I gave might not be (probably isn't) correct for this disc.
GreyRogue
Posts: 38
Joined: Sun Dec 07, 2014 2:57 am

Re: Datel AGP information

Post by GreyRogue » Sat Jan 17, 2015 8:31 pm

So, I bought a few used gameboy games just for fun. Unfortunately, it looks like I can't control the MBC (memory bank controller). I tried with both Cmd 7 and Cmd 6 (used Address = (0x2100 | (MemBank<<16)) and Val = 0x2100 to try to outsmart it). Neither of these methods appeared to work I don't know if this because of the voltage (3.3 instead of 5) or the CS2/Reset pin (see http://www.hardwarebook.info/Game_Pak).

Technically, you can read 0x0-3FFF and possibly 0x4000-0x7FFF depending on the MBC version for all carts, but I couldn't read the rest. However for the smallest games that don't have an MBC (size = 32K), using Cmd 4 to read the cartridge works. I successfully dumped Tennis and Tetris.
vinzanity
Posts: 24
Joined: Tue Jun 04, 2013 5:35 am

Re: Datel AGP information

Post by vinzanity » Mon Jan 19, 2015 2:55 am

GreyRogue wrote:I just noticed that these two videos show different versions of the AGP disc. The one I have has the construction kit and looks like this:
https://www.youtube.com/watch?v=WCc5EHj8_bw
(skip ahead to 1:15 to see the first menu). He doesn't show the built in games (press left and right when prompted to insert cartridge and press A), but I assume they're there.

This must be another version:
https://www.youtube.com/watch?v=YlU5gP5pUxs
Skip to 2:25 to see there is no Cheat Construction Kit selection. The list of disc offsets I gave might not be (probably isn't) correct for this disc.
I have the disc without the construction kit. Any chance I might have a copy of your modded cleanrip to see if it produces a working backup? Mine is really beat up and has cracks already in the center ring.
tueidj
Posts: 564
Joined: Fri May 03, 2013 6:57 am

Re: Datel AGP information

Post by tueidj » Fri Jan 30, 2015 2:09 am

From following the Dolphin pull request discussion, it appears the hash function is actually CRC8.
Post Reply