Skip to content
This repository has been archived by the owner on Jul 2, 2022. It is now read-only.

Francesco149/reversing-fgo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 

Repository files navigation

this is a diary of me reverse engineering fate grand order. this assumes you have read my love live all stars diary https://github.com/Francesco149/reversing-sifas and does not explain things already explained there. as usual this is uncut and observations might be corrected later

first things I do is look for strings that look like url's to find the config class that potentially holds other things like keys and stuff

this looks promising:

void ManagerConfig$$.cctor(void)

{
  int iVar1;
  int iVar2;
  
  if (DAT_027ed923 == '\0') {
    FUN_003479b0(0x5dc4);
    DAT_027ed923 = '\x01';
  }
  **(undefined4 **)(Class$ManagerConfig + 0x5c) = "2.4.2";
  iVar1 = Class$ManagerConfig;
  *(undefined4 *)(*(int *)(Class$ManagerConfig + 0x5c) + 4) =
       "f8a11e47a2423c919c09d0a22dcf0155847ca1943e09045b3035f6e318845d40";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 8) = "20191115_14:26";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0xc) = "Fgo_20150511_1";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x10) = "Fgo_20180629_1";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x14) = 0x42b40000;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x18) = 0x43160000;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x1c) = 0x3f800000;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x20) = 0x40000000;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x24) = 0x40000000;
  iVar2 = *(int *)(iVar1 + 0x5c);
  *(undefined4 *)(iVar2 + 0x28) = 0xe10;
  *(undefined4 *)(iVar2 + 0x2c) = 0;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x30) = 0x41200000;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x34) = 3;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x38) = 0x400;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x3c) = 0x240;
  iVar2 = *(int *)(iVar1 + 0x5c);
  *(undefined4 *)(iVar2 + 0x40) = 0x3200000;
  *(undefined4 *)(iVar2 + 0x44) = 0;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x48) = 0x3ba3d70a;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x4c) = "com.aniplex.fategrandorder";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x50) = "1015521325";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x54) = "Android";
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x58) = 1;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x5c) = "game.fate-go.jp";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x60) = "https://cdn.data.fate-go.jp/AssetStorages";
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 100) = "webview.fate-go.jp";
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x68) = 0;
  if (((*(byte *)(Class$string + 0xbf) & 2) != 0) && (*(int *)(Class$string + 0x70) == 0)) {
    FUN_0035803c();
  }
  iVar2 = Class$ManagerConfig;
  iVar1 = Class$string;
  *(undefined4 *)(*(int *)(Class$ManagerConfig + 0x5c) + 0x6c) =
       **(undefined4 **)(Class$string + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x70) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x74) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0x78) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x7c) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x80) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x84) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0x88) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x8c) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x90) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x94) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0x98) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x9c) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xa0) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xa4) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0xa8) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xac) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xb0) = **(undefined4 **)(iVar1 + 0x5c);
  iVar2 = Class$ManagerConfig;
  iVar1 = Class$string;
  *(undefined4 *)(*(int *)(Class$ManagerConfig + 0x5c) + 0xb4) =
       **(undefined4 **)(Class$string + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0xb8) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xbc) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xc0) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xc4) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 200) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xcc) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xd0) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xd4) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0xd8) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xdc) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xe0) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xe4) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0xe8) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xec) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0xf0) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xf4) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0xf8) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0xfc) = 0;
  iVar2 = Class$ManagerConfig;
  iVar1 = Class$string;
  *(undefined4 *)(*(int *)(Class$ManagerConfig + 0x5c) + 0x100) =
       **(undefined4 **)(Class$string + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x104) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x108) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x10c) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0x110) = 0;
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0x111) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x114) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x118) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 0x11c) = 0;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x120) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x124) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x128) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined *)(*(int *)(iVar2 + 0x5c) + 300) = 1;
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x130) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x134) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x138) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x13c) = **(undefined4 **)(iVar1 + 0x5c);
  *(undefined4 *)(*(int *)(iVar2 + 0x5c) + 0x140) = **(undefined4 **)(iVar1 + 0x5c);
  iVar1 = Class$ManagerConfig;
  *(undefined4 *)(*(int *)(Class$ManagerConfig + 0x5c) + 0x144) =
       **(undefined4 **)(Class$string + 0x5c);
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x148) = 1;
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x149) = 1;
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x14a) = 0;
  *(undefined4 *)(*(int *)(iVar1 + 0x5c) + 0x14c) = "RELEASE";
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x150) = *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x148);
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x151) = 0;
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x152) = 0;
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x153) = 0;
  *(undefined *)(*(int *)(iVar1 + 0x5c) + 0x154) = 0;
  return;
}

just need to clean it up by mapping the struct

those hex values are likely floats and you can easily confirm by setting them to float and verifying that they are meaningful numbers

this is what i mapped so far, don't really care about the rest for now

  Class$ManagerConfig->instance->version = "2.4.2";
  paVar2 = Class$ManagerConfig;
  Class$ManagerConfig->instance->someHash =
       "f8a11e47a2423c919c09d0a22dcf0155847ca1943e09045b3035f6e318845d40";
  paVar2->instance->buildDateTime = "20191115_14:26";
  paVar2->instance->buildNumber1 = "Fgo_20150511_1";
  paVar2->instance->buildNumber2 = "Fgo_20180629_1";
  paVar2->instance->float_0x14 = 90.00000000;
  paVar2->instance->float_0x18 = 150.00000000;
  paVar2->instance->float_0x1c = 1.00000000;
  paVar2->instance->float_0x20 = 2.00000000;
  paVar2->instance->float_0x24 = 2.00000000;
  pMVar3 = paVar2->instance;
  pMVar3->field_0x28 = 0xe10;
  pMVar3->field_0x2c = 0;
  paVar2->instance->float_0x30 = 10.00000000;
  paVar2->instance->field_0x34 = 3;
  paVar2->instance->field_0x38 = 0x400;
  paVar2->instance->field_0x3c = 0x240;
  pMVar3 = paVar2->instance;
  pMVar3->field_0x40 = 0x3200000;
  pMVar3->field_0x44 = 0;
  paVar2->instance->float_0x48 = 0.00500000;
  paVar2->instance->androidPackageName = "com.aniplex.fategrandorder";
  paVar2->instance->applePackageName = "1015521325";
  paVar2->instance->platform = "Android";
  *(undefined *)&paVar2->instance->field_0x58 = 1;
  paVar2->instance->endpointHost = "game.fate-go.jp";
  paVar2->instance->assetsEndpoint = "https://cdn.data.fate-go.jp/AssetStorages";
  paVar2->instance->webviewHost = "webview.fate-go.jp";
  *(undefined *)&paVar2->instance->field_0x68 = 0;

so i wonder what that hash is. let's search for references to it

looks like it's verCode:

  RequestBase$$addField(param_1,"lastAccessTime",(int)((ulonglong)uVar3 >> 0x20),(int)uVar3,0);
  verCode = Class$ManagerConfig->instance->someHash;
  if (param_1 == 0) {
    FUN_003733b8(0);
  }
  RequestBase$$addField(param_1,"verCode",verCode,0);

lots of other interesting fields in here:

void NetworkManager$$SetBaseField(int param_1)

{
  int iVar1;
  undefined4 uVar2;
  char *tmp1;
  undefined8 uVar3;
  
  if (DAT_027ee62e == '\0') {
    FUN_003479b0(0x65de);
    DAT_027ee62e = '\x01';
  }
  if (((*(byte *)(Class$NetworkManager + 0xbf) & 2) != 0) &&
     (*(int *)(Class$NetworkManager + 0x70) == 0)) {
    FUN_0035803c();
  }
  iVar1 = *(int *)(*(int *)(Class$NetworkManager + 0x5c) + 0x48);
  if (iVar1 != 0) {
    if (((*(byte *)(Class$NetworkManager + 0xbf) & 2) != 0) &&
       (*(int *)(Class$NetworkManager + 0x70) == 0)) {
      FUN_0035803c();
      iVar1 = *(int *)(*(int *)(Class$NetworkManager + 0x5c) + 0x48);
    }
    if (param_1 == 0) {
      FUN_003733b8(0);
    }
    RequestBase$$addField(param_1,"userId",iVar1,0);
    uVar2 = *(undefined4 *)(*(int *)(Class$NetworkManager + 0x5c) + 0x4c);
    if (param_1 == 0) {
      FUN_003733b8(0);
    }
    RequestBase$$addField(param_1,"authKey",uVar2,0);
  }
  if (((*(byte *)((int)&Class$ManagerConfig[1].instance + 3) & 2) != 0) &&
     (*(int *)&Class$ManagerConfig[1].field_0x10 == 0)) {
    FUN_0035803c();
  }
  tmp1 = Class$ManagerConfig->instance->appVer;
  if (param_1 == 0) {
    FUN_003733b8(0);
  }
  RequestBase$$addField(param_1,"appVer",tmp1,0);
  iVar1 = SingletonMonoBehaviour$$get_Instance
                    (Method$SingletonMonoBehaviour_DataManager_.get_Instance());
  if (iVar1 == 0) {
    FUN_003733b8(0);
  }
  uVar2 = DataManager$$getMasterDataVersion(iVar1,0);
  if (param_1 == 0) {
    FUN_003733b8(0);
  }
  RequestBase$$addField(param_1,"dataVer",uVar2,0);
  iVar1 = SingletonMonoBehaviour$$get_Instance
                    (Method$SingletonMonoBehaviour_DataManager_.get_Instance());
  if (iVar1 == 0) {
    FUN_003733b8(0);
  }
  uVar3 = DataManager$$getMasterDateVersion(iVar1,0);
  if (param_1 == 0) {
    FUN_003733b8(0);
  }
  RequestBase$$addField(param_1,"dateVer",(int)((ulonglong)uVar3 >> 0x20),(int)uVar3,0);
  if (((*(byte *)(Class$NetworkManager + 0xbf) & 2) != 0) &&
     (*(int *)(Class$NetworkManager + 0x70) == 0)) {
    FUN_0035803c();
  }
  uVar3 = NetworkManager$$getTime();
  if (param_1 == 0) {
    FUN_003733b8(0);
  }
  RequestBase$$addField(param_1,"lastAccessTime",(int)((ulonglong)uVar3 >> 0x20),(int)uVar3,0);
  tmp1 = Class$ManagerConfig->instance->verCode;
  if (param_1 == 0) {
    FUN_003733b8(0);
  }
  RequestBase$$addField(param_1,"verCode",tmp1,0);
  return;
}

this lets me map userId and authKey in the NetworkManager struct

  iVar1 = *(int *)&Class$NetworkManager->instance->userId;
  if (iVar1 != 0) {
    if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
      FUN_0035803c();
      iVar1 = *(int *)&Class$NetworkManager->instance->userId;
    }
    if (param_1 == 0) {
      FUN_003733b8(0);
    }
    RequestBase$$addField(param_1,"userId",iVar1,0);
    uVar2 = *(undefined4 *)&Class$NetworkManager->instance->authKey;
    if (param_1 == 0) {
      FUN_003733b8(0);
    }
    RequestBase$$addField(param_1,"authKey",uVar2,0);
  }

if we search for NetworkManager we can find a bunch of getters and setters we could use to map other fields but we don't care about everything

I mapped a few that looked interesting: GetSecurityServerSetting, renamed endpointHost to gameServerSetting to match the getter, renamed assetsEndpoint to dataServerSetting, renamed webviewHost to webServerSetting, serverSettingType, dataServerFolderName,

NetworkManager$$getWebUrl reveals a few query params

    uVar9 = NetworkManager$$getTime();
    pNVar2 = Class$NetworkManager->instance;
    uVar8 = *(undefined4 *)&pNVar2->field_0xac;
    uVar10 = __aeabi_ldivmod((int)((ulonglong)uVar9 >> 0x20),(int)uVar9,
                             *(undefined4 *)&pNVar2->field_0x8,*(undefined4 *)&pNVar2->field_0xc);
    if (param_1 == 0) {
      FUN_003733b8(0);
      iVar3 = String$$IndexOf(0,uVar8,0);
      FUN_003733b8(0);
      iVar1 = 0;
    }
    else {
      iVar3 = String$$IndexOf(param_1,uVar8,0);
      iVar1 = param_1;
    }
    iVar1 = String$$IndexOf(iVar1,"?",0);
    if (iVar3 < 0) {
      if (iVar1 < 0) {
        uVar8 = FUN_00381f7c(Class$long,&local_28);
        local_28 = (undefined4)((ulonglong)uVar9 >> 0x20);
        local_24 = (undefined4)uVar9;
        uVar10 = uVar9;
        if (((*(byte *)(Class$string + 0xbf) & 2) != 0) && (*(int *)(Class$string + 0x70) == 0)) {
          FUN_0035803c();
          local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
          local_24 = (undefined4)uVar10;
        }
        puVar4 = &"?lastAccessTime=";
      }
      else {
        if (param_1 == 0) {
          FUN_003733b8(0);
        }
        iVar1 = String$$IndexOf(param_1,"lastAccessTime=",0);
        if (-1 < iVar1) {
          return param_1;
        }
        uVar8 = FUN_00381f7c(Class$long,&local_28);
        local_28 = (undefined4)((ulonglong)uVar9 >> 0x20);
        local_24 = (undefined4)uVar9;
        uVar10 = uVar9;
        if (((*(byte *)(Class$string + 0xbf) & 2) != 0) && (*(int *)(Class$string + 0x70) == 0)) {
          FUN_0035803c();
          local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
          local_24 = (undefined4)uVar10;
        }
        puVar4 = &"&lastAccessTime=";
      }
    }
    else {
      if (iVar1 < 0) {
        uVar8 = FUN_00381f7c(Class$long,&local_28);
        local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
        local_24 = (undefined4)uVar10;
        if (((*(byte *)(Class$string + 0xbf) & 2) != 0) && (*(int *)(Class$string + 0x70) == 0)) {
          FUN_0035803c();
          local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
          local_24 = (undefined4)uVar10;
        }
        puVar4 = &"?v=";
      }
      else {
        uVar8 = FUN_00381f7c(Class$long,&local_28);
        local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
        local_24 = (undefined4)uVar10;
        if (((*(byte *)(Class$string + 0xbf) & 2) != 0) && (*(int *)(Class$string + 0x70) == 0)) {
          FUN_0035803c();
          local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
          local_24 = (undefined4)uVar10;
        }
        puVar4 = &"&v=";
      }
    }
    param_1 = String$$Concat(param_1,*puVar4,uVar8,0);
    local_28 = (undefined4)((ulonglong)uVar10 >> 0x20);
    local_24 = (undefined4)uVar10;
  }

interesting class name in NetworkManager$$GetMk

  uVar2 = CatAndMouseGame$$MouseGame1(uVar2,0,0);
  return uVar2;

looks like the il2cppdumper guy has already reverse engineered it some time ago

this also obfuscates the value returned by GetCv

these 2 values seem to be retrieved by MdcStr$$Ec . I couldn't find references to this but we can check later if needed

GetApiCode, GetAuthCode are also obfuscated by this

these are called in a huge huge function named NetworkManager._RequestCR_c__Iterator1$$MoveNext

this has a large switch which handles what looks like different types of http requests

skimming through this we can map a few NetworkManager fields:

  iVar15 = *(int *)&Class$NetworkManager->instance->userAgent;
  if (iVar15 != 0) {
    iVar8 = *(int *)(param_1 + 0x40);
    if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
      FUN_0035803c();
      iVar15 = *(int *)&Class$NetworkManager->instance->userAgent;
    }
    if (iVar8 == 0) {
      FUN_003733b8(0);
    }
    FUN_006eabe0(iVar8,"User-Agent",iVar15,Method$Dictionary_string_-string_.Add());
  }
  iVar15 = *(int *)&Class$NetworkManager->instance->cookie;
  if (iVar15 != 0) {
    iVar8 = *(int *)(param_1 + 0x40);
    if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
      FUN_0035803c();
      iVar15 = *(int *)&Class$NetworkManager->instance->cookie;
    }
    if (iVar8 == 0) {
      FUN_003733b8(0);
    }
    FUN_006eabe0(iVar8,"Cookie",iVar15,Method$Dictionary_string_-string_.Add());
  }

so it looks like they use their own http library and this iVar8 dict is being turned into form data:

  uVar2 = WWWForm$$get_data(iVar8,0);
  uVar20 = *(undefined4 *)(param_1 + 0x40);
  uVar18 = thunk_FUN_00382384(Class$DelightWorks.Network.BestWWW);
  BestWWW$$.ctor(uVar18,iVar15,uVar2,uVar20,0);

I guess I'll map out the HTTPRequest struct (instantiated in BestWWW) and maybe hook somewhere and log http traffic

so while googling some error strings in HTTPRequest I found the actual library they're using, it's called BestHTTP and it's proprietary. 60$ on the unity asset store. pretty hilarious to spend 60$ and lock yourself into a proprietary library for a simple http implementation lol

well at least the source is available so I can look at it for reference

the easiest method to hook seems to be HTTPManager$$SendRequestImpl . it has a rather unique name which makes it easy to find by scanning il2cpp metadata and it takes the HTTPRequest as a param, and it's clearly called when the request is sent.

for responses HTTPRequest$$CallCallback seems perfect

you can find my hooks here: https://github.com/Francesco149/fgohook

some requests take an authCode field. if we take a look again at NetworkManager._RequestCR_c__Iterator1$$MoveNext we see that it calls GetAuthCode passing it some field of whatever class is passed to this function.

  pvVar19 = (void *)param_1->field_0x38;
  if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
    FUN_0035803c();
  }
  iVar15 = NetworkManager$$GetAuthCode(pvVar19);
  param_1->field_0x58 = iVar15;
  if (iVar15 != 0) {
    iVar8 = param_1->field_0x3c;
    if (iVar8 == 0) {
      FUN_003733b8(0);
    }
    WWWForm$$AddField(iVar8,"authCode",iVar15,0);
  }

I'm pretty sure this is just a dict of the form fields, if we scroll up we see the same field being passed to getWWWForm:

  iVar15 = RequestBase$$getWWWForm(piVar17,&param_1->field_0x38,0);
  param_1->field_0x3c = iVar15;

ok, let's look at GetAuthCode. it took a bit of guessing to figure out the weird code il2cpp generates for array append/resize, but what it does is build a query string (might be unescaped, not sure yet) out of the form fields and then computes the sha1 of query_string:field_0x50 and returns the hash as a base64 string.

undefined4 NetworkManager$$GetAuthCode(void *param_1)

{
  Array_string_ *arrayOfStrings;
  String *tmps;
  undefined4 val;
  int it;
  int *piVar1;
  String *str;
  undefined4 key;
  void *pair;
  undefined4 uStack92;
  undefined8 local_48;
  undefined8 uStack64;
  undefined4 local_38;
  void *pair_;
  undefined4 local_2c;
  
  if (DAT_027ee62d == '\0') {
    FUN_003479b0(0x65b2);
    DAT_027ee62d = '\x01';
  }
  local_2c = 0;
  local_48 = 0;
  uStack64 = 0;
  pair_ = (void *)0x0;
  local_38 = 0;
  if (((Class$string->field_0xbf & 2) != 0) && (Class$string->field_0x70 == 0)) {
    FUN_0035803c();
  }
  str = Class$string->staticData->empty;
  if (param_1 == (void *)0x0) {
    throwNullPointer(0);
  }
  SortedDictionary$$GetEnumerator
            (&pair,param_1,Method$SortedDictionary_string_-string_.GetEnumerator());
  while (it = FUN_0201a200(&local_48,Method$SortedDictionary.Enumerator_string_-string_.MoveNext()),
        it != 0) {
    FUN_0201a1ec(&pair,&local_48,Method$SortedDictionary.Enumerator_string_-string_.get_Current());
    local_2c = uStack92;
    pair_ = pair;
    if (((Class$string->field_0xbf & 2) != 0) && (Class$string->field_0x70 == 0)) {
      FUN_0035803c();
    }
    it = String$$op_Inequality(str,Class$string->staticData->empty,0);
    if (it == 0) {
                    /* first iteration only: str=key + "=" + value */
      key = FUN_01dc7cec(&pair_,Method$KeyValuePair_string_-string_.get_Key());
      val = FUN_01dc7cfc(&pair_,Method$KeyValuePair_string_-string_.get_Value());
      if (((Class$string->field_0xbf & 2) != 0) && (Class$string->field_0x70 == 0)) {
        FUN_0035803c();
      }
      str = (String *)String$$Concat(str,key,"=",val,0);
    }
    else {
                    /* this whole block does str="".join([str, "&", key, "=", value]) */
      arrayOfStrings = (Array_string_ *)InstantiateArray(Class$string[],5);
      if (arrayOfStrings == (Array_string_ *)0x0) {
        throwNullPointer(0);
      }
      if ((str != (String *)0x0) && (it = FUN_003822bc(str,arrayOfStrings->vtable[8]), it == 0)) {
        key = FUN_00374a74();
        throw(key,0,0);
      }
      if (arrayOfStrings->length == 0) {
        key = FUN_00374328();
        throw(key,0,0);
      }
      arrayOfStrings->data[0] = str;
      if (("&" != (String *)0x0) && (it = FUN_003822bc("&",arrayOfStrings->vtable[8]), it == 0)) {
        key = FUN_00374a74();
        throw(key,0,0);
      }
      tmps = "&";
      if ((uint)arrayOfStrings->length < 2) {
        key = FUN_00374328();
        throw(key,0,0);
      }
      arrayOfStrings->data[1] = tmps;
      tmps = (String *)FUN_01dc7cec(&pair_,Method$KeyValuePair_string_-string_.get_Key());
      if ((tmps != (String *)0x0) && (it = FUN_003822bc(tmps,arrayOfStrings->vtable[8]), it == 0)) {
        key = FUN_00374a74();
        throw(key,0,0);
      }
      if ((uint)arrayOfStrings->length < 3) {
        key = FUN_00374328();
        throw(key,0,0);
      }
      arrayOfStrings->data[2] = tmps;
      if (("=" != (String *)0x0) && (it = FUN_003822bc("=",arrayOfStrings->vtable[8]), it == 0)) {
        key = FUN_00374a74();
        throw(key,0,0);
      }
      tmps = "=";
      if ((uint)arrayOfStrings->length < 4) {
        key = FUN_00374328();
        throw(key,0,0);
      }
      arrayOfStrings->data[3] = tmps;
      tmps = (String *)FUN_01dc7cfc(&pair_,Method$KeyValuePair_string_-string_.get_Value());
      if ((tmps != (String *)0x0) && (it = FUN_003822bc(tmps,arrayOfStrings->vtable[8]), it == 0)) {
        key = FUN_00374a74();
        throw(key,0,0);
      }
      if ((uint)arrayOfStrings->length < 5) {
        key = FUN_00374328();
        throw(key,0,0);
      }
      arrayOfStrings->data[4] = tmps;
      if (((Class$string->field_0xbf & 2) != 0) && (Class$string->field_0x70 == 0)) {
        FUN_0035803c();
      }
      str = (String *)String$$Concat(arrayOfStrings,0);
    }
  }
  FUN_0201a400(&local_48,Method$SortedDictionary.Enumerator_string_-string_.Dispose());
  if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
    FUN_0035803c();
  }
  if (*(int *)&Class$NetworkManager->instance->field_0x50 == 0) {
    key = 0;
  }
  else {
    it = thunk_FUN_00382384(Class$System.Security.Cryptography.SHA1CryptoServiceProvider);
    SHA1CryptoServiceProvider$$.ctor(it,0);
    piVar1 = (int *)thunk_FUN_00382384(Class$System.Text.UTF8Encoding);
    UTF8Encoding$$.ctor(piVar1,0);
    if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
      FUN_0035803c();
    }
    key = *(undefined4 *)&Class$NetworkManager->instance->field_0x50;
    if (((Class$string->field_0xbf & 2) != 0) && (Class$string->field_0x70 == 0)) {
      FUN_0035803c();
    }
    key = String$$Concat(str,":",key,0);
    if (piVar1 == (int *)0x0) {
      throwNullPointer(0);
    }
    key = (**(code **)(*piVar1 + 0x110))(piVar1,key,*(undefined4 *)(*piVar1 + 0x114));
    if (it == 0) {
      it = 0;
      throwNullPointer(0);
      key = HashAlgorithm$$ComputeHash(0,key,0);
      throwNullPointer(0);
    }
    else {
      key = HashAlgorithm$$ComputeHash(it,key,0);
    }
    HashAlgorithm$$Clear(it,0);
    if (((*(byte *)(Class$System.Convert + 0xbf) & 2) != 0) &&
       (*(int *)(Class$System.Convert + 0x70) == 0)) {
      FUN_0035803c();
    }
    key = Convert$$ToBase64String(key,0);
  }
  return key;
}

great, now let's look for references to field 0x50 and see where it comes from

void NetworkManager$$SetAuth(undefined4 param_1,int param_2,undefined4 param_3,undefined4 param_4)

{
  astruct_1 *paVar1;
  
  if (DAT_027ee63a == '\0') {
    FUN_003479b0(0x65dd);
    DAT_027ee63a = '\x01';
  }
  if (param_2 == 0) {
    return;
  }
  if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
    FUN_0035803c();
  }
  paVar1 = Class$NetworkManager;
  *(int *)&Class$NetworkManager->instance->userId = param_2;
  *(undefined4 *)&paVar1->instance->authKey = param_3;
  *(undefined4 *)&paVar1->instance->field_0x50 = param_4;
  return;
}

this is called from AccountRegistRequest$$requestCompleted

    item = (int *)FUN_006e96c4(iVar1,"userId",Method$Dictionary_string_-object_.get_Item());
    if (item == (int *)0x0) {
      throwNullPointer(0);
    }
    userId = (**(code **)(*item + 0xd8))(item,*(undefined4 *)(*item + 0xdc));
    item = (int *)FUN_006e96c4(iVar1,"authKey",Method$Dictionary_string_-object_.get_Item());
    if (item == (int *)0x0) {
      throwNullPointer(0);
    }
    authKey = (**(code **)(*item + 0xd8))(item,*(undefined4 *)(*item + 0xdc));
    item = (int *)FUN_006e96c4(iVar1,"secretKey",Method$Dictionary_string_-object_.get_Item());
    if (item == (int *)0x0) {
      throwNullPointer(0);
    }
    secretKey = (**(code **)(*item + 0xd8))(item,*(undefined4 *)(*item + 0xdc));
    iVar1 = SingletonMonoBehaviour$$get_Instance
                      (Method$SingletonMonoBehaviour_NetworkManager_.get_Instance());
    if (iVar1 == 0) {
      throwNullPointer(0);
    }
    NetworkManager$$SetAuth(iVar1,userId,authKey,secretKey,0);

okay, it's just the secretKey. pretty straightforward. I implemented this and tested it and sure enough creating accounts works, it's only a matter at looking at the http log from my hooks and mimick those requests

so the login request takes a few interesting fields:

void TopLoginRequest$$beginRequest(int param_1)

{
  uint y;
  undefined4 dataServerFolder;
  int *utf8;
  undefined4 str;
  uint dataServerFolderCrc32;
  uint userId;
  uint x;
  int userState;
  undefined8 lastAccessTime;
  
  if (DAT_027eee68 == '\0') {
    FUN_003479b0(0xa033);
    DAT_027eee68 = '\x01';
  }
  RequestBase$$addBaseField(param_1,0);
  if (((Class$NetworkManager->field_0xbf & 2) != 0) && (Class$NetworkManager->field_0x70 == 0)) {
    FUN_0035803c();
  }
  dataServerFolder = NetworkManager$$GetDataServerFolderName(0);
  if (((*(byte *)(Class$System.Text.Encoding + 0xbf) & 2) != 0) &&
     (*(int *)(Class$System.Text.Encoding + 0x70) == 0)) {
    FUN_0035803c();
  }
  utf8 = (int *)Encoding$$get_UTF8(0);
  if (utf8 == (int *)0x0) {
    throwNullPointer(0);
  }
  str = (**(code **)(*utf8 + 0x110))(utf8,dataServerFolder,*(undefined4 *)(*utf8 + 0x114));
  if (((*(byte *)(Class$Crc32 + 0xbf) & 2) != 0) && (*(int *)(Class$Crc32 + 0x70) == 0)) {
    FUN_0035803c();
  }
  dataServerFolderCrc32 = Crc32$$Compute(str,0);
  userId = NetworkManager$$get_UserId(0);
  userState = *(int *)(param_1 + 0x10);
  if (userState == 0) {
    throwNullPointer(0);
  }
  str = FUN_006e96c4(userState,"lastAccessTime",Method$Dictionary_string_-string_.get_Item());
  lastAccessTime = Int64$$Parse(str,0);
  x = ~(uint)((ulonglong)lastAccessTime >> 0x20);
  y = -((int)~(uint)lastAccessTime >> 0x1f);
  userState = ~(uint)lastAccessTime + (uint)CARRY4(x,y);
  str = 0;
                    /* ^ this is probably a 64-bit operation
                       CARRY4(x,y) - true if there would be a carry adding x to y
                       
                       addField takes loword and hiword for a 64-bit int */
  RequestBase$$addField
            (param_1,"userState",
             (x + y >> 2 | userState * 0x40000000) ^ userId & dataServerFolderCrc32,userState >> 2);
  RequestBase$$addField(param_1,"assetbundleFolder",dataServerFolder,0,str);
  dataServerFolder = NetworkManager$$GetTerminalDispState(0);
  RequestBase$$addField(param_1,"isTerminalLogin",dataServerFolder,0);
  NetworkManager$$RequestStart(param_1,0);
  return;
}

NetworkManager$$GetTerminalDispState is just some check on the scene id, we can probably just hardcode it for each request

let's find out how dataServerFolderName is generated

in TopGameDataRequest$$requestCompleted :

    iVar2 = FUN_006eb2a0(iVar1,"assetbundle",Method$Dictionary_string_-object_.ContainsKey());
    if (iVar2 == 1) {
      piVar3 = (int *)FUN_006e96c4(iVar1,"assetbundle",Method$Dictionary_string_-object_.get_Item())
      ;
      if (piVar3 == (int *)0x0) {
        throwNullPointer(0);
      }
      uVar5 = (**(code **)(*piVar3 + 0xd8))(piVar3,*(undefined4 *)(*piVar3 + 0xdc));
      if (((*(byte *)(Class$System.Convert + 0xbf) & 2) != 0) &&
         (*(int *)(Class$System.Convert + 0x70) == 0)) {
        FUN_0035803c();
      }
      pAVar6 = (Array *)Convert$$FromBase64String(uVar5,0);
      if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0)
         ) {
        FUN_0035803c();
      }
      uVar5 = CatAndMouseGame$$MouseInfoMsgPack(pAVar6);
      if (((*(byte *)(Class$JsonManager + 0xbf) & 2) != 0) &&
         (*(int *)(Class$JsonManager + 0x70) == 0)) {
        FUN_0035803c();
      }
      iVar2 = JsonManager$$getDictionary(uVar5,0);
      if (iVar2 != 0) {
        iVar7 = FUN_006eb2a0(iVar2,"folderName",Method$Dictionary_string_-object_.ContainsKey());
        if (iVar7 == 1) {
          piVar3 = (int *)FUN_006e96c4(iVar2,"folderName",
                                       Method$Dictionary_string_-object_.get_Item());
          if (piVar3 == (int *)0x0) {
            throwNullPointer(0);
          }
          uVar5 = (**(code **)(*piVar3 + 0xd8))(piVar3,*(undefined4 *)(*piVar3 + 0xdc));
          if (((Class$NetworkManager->field_0xbf & 2) != 0) &&
             (Class$NetworkManager->field_0x70 == 0)) {
            FUN_0035803c();
          }
          NetworkManager$$SetDataServerFolderName(uVar5,0);
        }
        iVar7 = FUN_006eb2a0(iVar2,"animalName",Method$Dictionary_string_-object_.ContainsKey());
        if (iVar7 == 1) {
          piVar3 = (int *)FUN_006e96c4(iVar2,"animalName",
                                       Method$Dictionary_string_-object_.get_Item());
          if (piVar3 == (int *)0x0) {
            throwNullPointer(0);
          }
          uVar5 = (**(code **)(*piVar3 + 0xd8))(piVar3,*(undefined4 *)(*piVar3 + 0xdc));
          if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) &&
             (Class$CatAndMouseGame->field_0x70 == 0)) {
            FUN_0035803c();
          }
          CatAndMouseGame$$ThirdHomeBuilding(uVar5,0);
        }
        iVar7 = FUN_006eb2a0(iVar2,"zooName",Method$Dictionary_string_-object_.ContainsKey());
        if (iVar7 == 1) {
          piVar3 = (int *)FUN_006e96c4(iVar2,"zooName",Method$Dictionary_string_-object_.get_Item())
          ;
          if (piVar3 == (int *)0x0) {
            throwNullPointer(0);
          }
          uVar5 = (**(code **)(*piVar3 + 0xd8))(piVar3,*(undefined4 *)(*piVar3 + 0xdc));
          if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) &&
             (Class$CatAndMouseGame->field_0x70 == 0)) {
            FUN_0035803c();
          }
          CatAndMouseGame$$ForthHomeBuilding(uVar5,0);
        }
      }
    }

looks like this is where that CatAndMouseGame class comes into play, it decodes and gets some parameters from assetbundle field

not an issue, CatAndMouseGame has already been reversed and it wouldn't be hard to reverse it myself.

void CatAndMouseGame$$MouseInfoMsgPack(Array *data)

{
  astruct_4 *catAndMouse;
  undefined4 tmp;
  int *piVar1;
  Array *rgbKey;
  
  if (DAT_027ee855 == '\0') {
    FUN_003479b0(0x1d2d);
    DAT_027ee855 = '\x01';
  }
  if (data == (Array *)0x0) {
    throwNullPointer(0);
  }
  tmp = InstantiateArray(Class$byte[],data->length + -0x20);
  if (((*(byte *)(Class$System.Text.Encoding + 0xbf) & 2) != 0) &&
     (*(int *)(Class$System.Text.Encoding + 0x70) == 0)) {
    FUN_0035803c();
  }
  piVar1 = (int *)Encoding$$get_UTF8(0);
  if (piVar1 == (int *)0x0) {
    throwNullPointer(0);
  }
  rgbKey = (Array *)(**(code **)(*piVar1 + 0x110))
                              (piVar1,"W0Juh4cFJSYPkebJB9WpswNF51oa6Gm7",
                               *(undefined4 *)(*piVar1 + 0x114));
  if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0)) {
    FUN_0035803c();
  }
  catAndMouse = Class$CatAndMouseGame;
  Class$CatAndMouseGame->staticData->rgbKey = rgbKey;
                    /* void Copy (Array sourceArray, long sourceIndex, Array destinationArray, long
                       destinationIndex, long length) */
  Array$$Copy(data,0,catAndMouse->staticData->rgbIV,0,0x20,0);
  Array$$Copy(data,0x20,tmp,0,data->length + -0x20,0);
  CatAndMouseGame$$MouseHomeMsgPack
            (tmp,Class$CatAndMouseGame->staticData->rgbKey,Class$CatAndMouseGame->staticData->rgbIV,
             1);
  return;
}

I already looked ahead and I know those 2 params are rgbKey and rgbIV for the decryptor. so the first 32 bytes of the data are the IV, the rest is the data and the rgbKey is a fixed string

here they use this library: https://github.com/shogo82148/MiniMessagePack

to decode MsgPack data

void CatAndMouseGame$$MouseHomeMsgPack(Array *data,Array *rgbKey,Array *rgbIV,bool param_4)

{
  int packer;
  undefined4 msgPackData;
  
  if (DAT_027ee869 == '\0') {
    FUN_003479b0(0x1d2a);
    DAT_027ee869 = '\x01';
  }
  packer = thunk_FUN_00382384(Class$MiniMessagePack.MiniMessagePacker);
  MiniMessagePacker$$.ctor(packer,0);
  if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0)) {
    FUN_0035803c();
  }
  msgPackData = CatAndMouseGame$$MouseHomeSub(data,rgbKey,rgbIV,param_4);
  if (packer == 0) {
    throwNullPointer(0);
  }
  MiniMessagePacker$$Unpack(packer,msgPackData,0);
  return;
}

MouseHomeSub looks exactly like MouseHomeMain in that Fgo repository https://github.com/Hengle/Fgo/blob/master/CatAndMouseGame.cs#L261

Array * CatAndMouseGame$$MouseHomeSub(Array *data,Array *rgbKey,Array *rgbIV,bool isPress)

{
  undefined4 transform;
  Array *buffer;
  int *stream2;
  int *stream3;
  int *stream5;
  Array *buffer2;
  int iVar1;
  int *stream;
  undefined4 auStack56 [2];
  undefined4 *local_30;
  int *local_2c;
  
  if (DAT_027ee86f == '\0') {
    FUN_003479b0(0x1d2b);
    DAT_027ee86f = '\x01';
  }
  local_30 = auStack56;
  local_2c = (int *)0x0;
  stream = (int *)thunk_FUN_00382384(Class$System.Security.Cryptography.RijndaelManaged);
  RijndaelManaged$$.ctor(stream,0);
  if (stream == (int *)0x0) {
    throwNullPointer(0);
  }
                    /* PaddingMode.PKCS7 */
  (**(code **)(*stream + 0x168))(stream,2,*(undefined4 *)(*stream + 0x16c));
                    /* CipherMode.CBC */
  (**(code **)(*stream + 0x158))(stream,1,*(undefined4 *)(*stream + 0x15c));
                    /* KeySize = 0x100 */
  (**(code **)(*stream + 0x138))(stream,0x100,*(undefined4 *)(*stream + 0x13c));
                    /* BlockSize = 0x100 */
  (**(code **)(*stream + 0xf8))(stream,0x100,*(undefined4 *)(*stream + 0xfc));
                    /* CreateDecryptor */
  transform = (**(code **)(*stream + 0x178))(stream,rgbKey,rgbIV,*(undefined4 *)(*stream + 0x17c));
  if (data == (Array *)0x0) {
    throwNullPointer(0);
  }
  buffer = (Array *)InstantiateArray(Class$byte[],data->length);
  stream = (int *)thunk_FUN_00382384(Class$System.IO.MemoryStream);
  MemoryStream$$.ctor(stream,data,0);
  stream2 = (int *)thunk_FUN_00382384(Class$System.Security.Cryptography.CryptoStream);
  CryptoStream$$.ctor(stream2,stream,transform,0,0);
  if (buffer == (Array *)0x0) {
    throwNullPointer(0);
  }
  if (stream2 == (int *)0x0) {
    throwNullPointer(0);
  }
                    /* Read */
  (**(code **)(*stream2 + 0x158))(stream2,buffer,0,buffer->length,*(undefined4 *)(*stream2 + 0x15c))
  ;
  if (isPress != false) {
    local_2c = stream;
    stream3 = (int *)thunk_FUN_00382384(Class$System.IO.MemoryStream);
    MemoryStream$$.ctor(stream3,0);
    stream = (int *)thunk_FUN_00382384(Class$System.IO.MemoryStream);
    MemoryStream$$.ctor(stream,buffer,0);
    stream5 = (int *)thunk_FUN_00382384(Class$ICSharpCode.SharpZipLib.GZip.GZipInputStream);
    GZipInputStream$$.ctor(stream5,stream,0);
    buffer2 = (Array *)InstantiateArray(Class$byte[],0x4000);
    while( true ) {
      if (buffer2 == (Array *)0x0) {
        throwNullPointer(0);
      }
      if (stream5 == (int *)0x0) {
        throwNullPointer(0);
      }
                    /* Read */
      iVar1 = (**(code **)(*stream5 + 0x158))
                        (stream5,buffer2,0,buffer2->length,*(undefined4 *)(*stream5 + 0x15c));
      if (iVar1 < 1) break;
      if (stream3 == (int *)0x0) {
        throwNullPointer(0);
      }
                    /* Write */
      (**(code **)(*stream3 + 0x178))(stream3,buffer2,0,iVar1,*(undefined4 *)(*stream3 + 0x17c));
    }
                    /* Close */
    (**(code **)(*stream5 + 0x128))(stream5,*(undefined4 *)(*stream5 + 300));
    if (stream3 == (int *)0x0) {
      throwNullPointer(0);
    }
                    /* ToArray */
    buffer = (Array *)(**(code **)(*stream3 + 0x1c0))(stream3,*(undefined4 *)(*stream3 + 0x1c4));
    if (stream == (int *)0x0) {
      throwNullPointer(0);
    }
                    /* Close */
    (**(code **)(*stream + 0x128))(stream,*(undefined4 *)(*stream + 300));
    stream = local_2c;
                    /* Close */
    (**(code **)(*stream3 + 0x128))(stream3,*(undefined4 *)(*stream3 + 300));
  }
  *local_30 = 0xfc;
  if (stream != (int *)0x0) {
    (**(code **)(*stream + 0x128))(stream,*(undefined4 *)(*stream + 300));
  }
  if (stream2 != (int *)0x0) {
    (**(code **)(*stream2 + 0x128))(stream2,*(undefined4 *)(*stream2 + 300));
  }
  return buffer;
}

yeah, this shouldn't be a problem to implement, just a bit tedious to stitch together

I wrote this basic test with hardcoded data from my game's requests and sure enough, it spits out an object with the folderName, animalName, zooName fields, and the folderName matches the value in my request

  assetbundle = b64decode("...")
  rgb_iv = assetbundle[:32]
  rgb_key = "W0Juh4cFJSYPkebJB9WpswNF51oa6Gm7"
  encrypted = assetbundle[32:]
  rjn = RijndaelCbc(
      key=rgb_key,
      iv=rgb_iv,
      padding=Pkcs7Padding(32),
      block_size=32
  )
  decrypted = rjn.decrypt(encrypted)
  decrypted = gzip.decompress(decrypted)
  print(umsgpack.unpackb(decrypted))

so, for user_state i did a few tests and this seems to yield correct results though I'm not sure it's 100% correct yet

  crc = binascii.crc32(j["folderName"].encode("utf-8"))
  user_state = ~(last_access_time >> 2) ^ user_id & crc

I ended up hooking ComputeHashData to verify what's being hashed for the authCode because my codes were not matching. it turns out that the game sorts the form fields alphabetically before hashing for the authCode

also the sorted form fields in the hashed data should NOT be url-encoded. after these changes my hashes match and everything works as expected

okay so i got to the battle result request and oh boy this thing has a huge result field that's encrypted with CatAndMouseGame$$CatGame5 . I'm not gonna paste the entire thing, but it contains info about various items and some checksums that depend on items and userId

let's focus on implementing CatGame5 first so we can decode the contents

seems to be the same encryption as before but no msgpack and rgbKey is xored with 4 and rgbIV is xored with 8, also no compression. also, if compression was enabled it would be bzip instead of gzip

void CatAndMouseGame$$CatGame5(undefined4 param_1)

{
  byte bVar1;
  int rgbKey2;
  int rgbIV2;
  int *piVar2;
  undefined4 data;
  undefined4 uVar3;
  Array *pAVar4;
  int iVar5;
  uint uVar6;
  
  if (DAT_027ee84e == '\0') {
    FUN_003479b0(0x1d07);
    DAT_027ee84e = '\x01';
  }
  if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0)) {
    FUN_0035803c();
  }
  pAVar4 = Class$CatAndMouseGame->staticData->rgbKey2;
  if (pAVar4 == (Array *)0x0) {
    throwNullPointer(0);
  }
  rgbKey2 = InstantiateArray(Class$byte[],pAVar4->length);
  pAVar4 = Class$CatAndMouseGame->staticData->rgbIV2;
  if (pAVar4 == (Array *)0x0) {
    throwNullPointer(0);
  }
  rgbIV2 = InstantiateArray(Class$byte[],pAVar4->length);
  if (((*(byte *)(Class$System.Text.Encoding + 0xbf) & 2) != 0) &&
     (*(int *)(Class$System.Text.Encoding + 0x70) == 0)) {
    FUN_0035803c();
  }
  piVar2 = (int *)Encoding$$get_UTF8(0);
  if (piVar2 == (int *)0x0) {
    throwNullPointer(0);
  }
  data = (**(code **)(*piVar2 + 0x110))(piVar2,param_1,*(undefined4 *)(*piVar2 + 0x114));
  iVar5 = 0x10;
  while( true ) {
    if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0))
    {
      FUN_0035803c();
    }
    uVar6 = iVar5 - 0x10;
    pAVar4 = Class$CatAndMouseGame->staticData->rgbKey2;
    if (pAVar4 == (Array *)0x0) {
      throwNullPointer(0);
    }
    if (pAVar4->length <= (int)uVar6) break;
    if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0))
    {
      FUN_0035803c();
    }
    pAVar4 = Class$CatAndMouseGame->staticData->rgbKey2;
    if (pAVar4 == (Array *)0x0) {
      throwNullPointer(0);
    }
    if ((uint)pAVar4->length <= uVar6) {
      uVar3 = IndexOutOfRangeException();
      throw(uVar3,0,0);
    }
    bVar1 = *(byte *)(&pAVar4->field_0x0 + iVar5);
    if (rgbKey2 == 0) {
      throwNullPointer(0);
    }
    if (*(uint *)(rgbKey2 + 0xc) <= uVar6) {
      uVar3 = IndexOutOfRangeException();
      throw(uVar3,0,0);
    }
    *(byte *)(rgbKey2 + iVar5) = bVar1 ^ 4;
    iVar5 = iVar5 + 1;
  }
  iVar5 = 0x10;
  while( true ) {
    if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0))
    {
      FUN_0035803c();
    }
    uVar6 = iVar5 - 0x10;
    pAVar4 = Class$CatAndMouseGame->staticData->rgbIV2;
    if (pAVar4 == (Array *)0x0) {
      throwNullPointer(0);
    }
    if (pAVar4->length <= (int)uVar6) break;
    if (((*(ushort *)&Class$CatAndMouseGame->field_0xbe & 0x200) != 0) &&
       (Class$CatAndMouseGame->field_0x70 == 0)) {
      FUN_0035803c();
    }
    pAVar4 = Class$CatAndMouseGame->staticData->rgbIV2;
    if (pAVar4 == (Array *)0x0) {
      throwNullPointer(0);
    }
    if ((uint)pAVar4->length <= uVar6) {
      uVar3 = IndexOutOfRangeException();
      throw(uVar3,0,0);
    }
    bVar1 = *(byte *)(&pAVar4->field_0x0 + iVar5);
    if (rgbIV2 == 0) {
      throwNullPointer(0);
    }
    if (*(uint *)(rgbIV2 + 0xc) <= uVar6) {
      uVar3 = IndexOutOfRangeException();
      throw(uVar3,0,0);
    }
    *(byte *)(rgbIV2 + iVar5) = bVar1 ^ 8;
    iVar5 = iVar5 + 1;
  }
  if (((*(ushort *)&Class$CatAndMouseGame->field_0xbe & 0x200) != 0) &&
     (Class$CatAndMouseGame->field_0x70 == 0)) {
    FUN_0035803c();
  }
  CatAndMouseGame$$CatHome(data,rgbKey2,rgbIV2,0);
  return;
}

undefined4
CatAndMouseGame$$CatHome(undefined4 data,undefined4 rgbKey2,undefined4 rgbIV2,undefined4 compressed)

{
  int iVar1;
  undefined4 uVar2;
  
  if (DAT_027ee863 == '\0') {
    FUN_003479b0(0x1d11);
    DAT_027ee863 = '\x01';
  }
  if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0)) {
    FUN_0035803c();
  }
  iVar1 = CatAndMouseGame$$CatHomeMain(data,rgbKey2,rgbIV2,compressed);
  if (iVar1 != 0) {
    if (((*(byte *)(Class$System.Convert + 0xbf) & 2) != 0) &&
       (*(int *)(Class$System.Convert + 0x70) == 0)) {
      FUN_0035803c();
    }
    uVar2 = Convert$$ToBase64String(iVar1,0);
    return uVar2;
  }
  return 0;
}

undefined4
CatAndMouseGame$$CatHomeMain(int data,undefined4 rgbKey2,undefined4 rgbIV2,int compressed)

{
  undefined4 *puVar1;
  int *piVar2;
  undefined4 uVar3;
  int *piVar4;
  int *piVar5;
  undefined4 auStack56 [3];
  undefined4 *local_2c;
  
  if (DAT_027ee867 == '\0') {
    FUN_003479b0(0x1d0f);
    DAT_027ee867 = '\x01';
  }
  local_2c = auStack56;
  piVar2 = (int *)thunk_FUN_00382384(Class$System.Security.Cryptography.RijndaelManaged);
  RijndaelManaged$$.ctor(piVar2,0);
  if (piVar2 == (int *)0x0) {
    throwNullPointer(0);
  }
  (**(code **)(*piVar2 + 0x168))(piVar2,2,*(undefined4 *)(*piVar2 + 0x16c));
  (**(code **)(*piVar2 + 0x158))(piVar2,1,*(undefined4 *)(*piVar2 + 0x15c));
  (**(code **)(*piVar2 + 0x138))(piVar2,0x100,*(undefined4 *)(*piVar2 + 0x13c));
  (**(code **)(*piVar2 + 0xf8))(piVar2,0x100,*(undefined4 *)(*piVar2 + 0xfc));
  uVar3 = (**(code **)(*piVar2 + 0x188))(piVar2,rgbKey2,rgbIV2,*(undefined4 *)(*piVar2 + 0x18c));
  if (data == 0) {
    throwNullPointer(0);
  }
  piVar2 = (int *)thunk_FUN_00382384(Class$System.IO.MemoryStream);
  MemoryStream$$.ctor(piVar2,((*(int *)(data + 0xc) + 0xff) / 0x100) * 0x100,0);
  piVar4 = (int *)thunk_FUN_00382384(Class$System.Security.Cryptography.CryptoStream);
  CryptoStream$$.ctor(piVar4,piVar2,uVar3,1,0);
  puVar1 = local_2c;
  if (compressed == 0) {
    if (piVar4 == (int *)0x0) {
      throwNullPointer(0);
    }
    (**(code **)(*piVar4 + 0x178))
              (piVar4,data,0,*(undefined4 *)(data + 0xc),*(undefined4 *)(*piVar4 + 0x17c));
    CryptoStream$$FlushFinalBlock(piVar4,0);
  }
  else {
    piVar5 = (int *)thunk_FUN_00382384(Class$ICSharpCode.SharpZipLib.BZip2.BZip2OutputStream);
    BZip2OutputStream$$.ctor(piVar5,piVar4,1,0);
    if (piVar5 == (int *)0x0) {
      throwNullPointer(0);
    }
    (**(code **)(*piVar5 + 0x178))
              (piVar5,data,0,*(undefined4 *)(data + 0xc),*(undefined4 *)(*piVar5 + 0x17c));
    (**(code **)(*piVar5 + 0x128))(piVar5,*(undefined4 *)(*piVar5 + 300));
  }
  if (piVar2 == (int *)0x0) {
    throwNullPointer(0);
  }
  uVar3 = (**(code **)(*piVar2 + 0x1c0))(piVar2,*(undefined4 *)(*piVar2 + 0x1c4));
  *puVar1 = 0xc3;
  (**(code **)(*piVar2 + 0x128))(piVar2,*(undefined4 *)(*piVar2 + 300));
  if (piVar4 != (int *)0x0) {
    (**(code **)(*piVar4 + 0x128))(piVar4,*(undefined4 *)(*piVar4 + 300));
  }
  return uVar3;
}

most likely rgbKey2 and rgbIV2 are set by the other two fields in that msgpack response from earlier

        iVar7 = FUN_006eb2a0(iVar2,"animalName",Method$Dictionary_string_-object_.ContainsKey());
        if (iVar7 == 1) {
          piVar3 = (int *)FUN_006e96c4(iVar2,"animalName",
                                       Method$Dictionary_string_-object_.get_Item());
          if (piVar3 == (int *)0x0) {
            throwNullPointer(0);
          }
          uVar5 = (**(code **)(*piVar3 + 0xd8))(piVar3,*(undefined4 *)(*piVar3 + 0xdc));
          if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) &&
             (Class$CatAndMouseGame->field_0x70 == 0)) {
            FUN_0035803c();
          }
          CatAndMouseGame$$ThirdHomeBuilding(uVar5,0);
        }
        iVar7 = FUN_006eb2a0(iVar2,"zooName",Method$Dictionary_string_-object_.ContainsKey());
        if (iVar7 == 1) {
          piVar3 = (int *)FUN_006e96c4(iVar2,"zooName",Method$Dictionary_string_-object_.get_Item())
          ;
          if (piVar3 == (int *)0x0) {
            throwNullPointer(0);
          }
          uVar5 = (**(code **)(*piVar3 + 0xd8))(piVar3,*(undefined4 *)(*piVar3 + 0xdc));
          if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) &&
             (Class$CatAndMouseGame->field_0x70 == 0)) {
            FUN_0035803c();
          }
          CatAndMouseGame$$ForthHomeBuilding(uVar5,0);
        }

animalName sets the value of rgbKey2 and rgbIV2 as well as some third key/iv pair. zooName sets yet another key/iv pair that we haven't yet encountered

we only care about the part that sets key2/iv2 for now:

void CatAndMouseGame$$ThirdHomeBuilding(undefined4 animalName)

{
  int iVar1;
  Array *utf8data;
  uint utf8datalen;
  undefined4 uVar2;
  code *pcVar3;
  char b;
  Array *arr;
  uint i;
  
  /* .... */
  iVar1 = Application$$get_isPlaying(0);
  if (iVar1 == 1) {
    /* .... */
    utf8data = (Array *)Encoding$$get_UTF8(0);
    /* .... */
    pcVar3 = *(code **)(*(int *)utf8data + 0x110);
    uVar2 = *(undefined4 *)(*(int *)utf8data + 0x114);
  }
  /* .... */
  utf8data = (Array *)(*pcVar3)(utf8data,animalName,uVar2);
  if (((Class$CatAndMouseGame->field_0xbf & 2) != 0) && (Class$CatAndMouseGame->field_0x70 == 0)) {
    FUN_0035803c();
  }
  Array$$Copy(utf8data,0,Class$CatAndMouseGame->staticData->rgbKey2,0,0x20,0);
  arr = Class$CatAndMouseGame->staticData->rgbIV2;
  if (utf8data == (Array *)0x0) {
    throwNullPointer(0);
  }
  Array$$Copy(utf8data,0x20,arr,0,utf8data->length + -0x20,0);
  /* .... */

first 32 bytes are the rgbKey,rest is the rgbIV

I implemented this alongside my previous msgpack and userState tests, and it works - I can now decrypt the battle result field from my requests log:

  assetbundle = CENSORED
  k = "W0Juh4cFJSYPkebJB9WpswNF51oa6Gm7"
  j = FateGOHttpApi.mouse_info_msgpack(k, assetbundle)
  crc = binascii.crc32(j["folderName"].encode("utf-8"))
  last_access_time = CENSORED
  user_id = CENSORED
  user_state = ~(last_access_time >> 2) ^ user_id & crc
  print(j)
  print(user_state)

  # data sent in battle result request
  result = unquote(CENSORED)
  rgb_key = bytes([x ^ 4 for x in j["animalName"][:32].encode("utf-8")])
  rgb_iv = bytes([x ^ 8 for x in j["animalName"][32:].encode("utf-8")])
  rjn = RijndaelCbc(
      key=rgb_key,
      iv=rgb_iv,
      padding=Pkcs7Padding(32),
      block_size=32
  )
  decrypted = rjn.decrypt(b64decode(result))
  print(json.dumps(json.loads(decrypted), indent=2))

and the output:

{'folderName': 'CENSORED', 'animalName': 'CENSORED', 'zooName': 'CENSORED'}
CENSORED
{
  "battleId": CENSORED,
  "battleResult": 1,
  "winResult": 1,
  "scores": "",
  "action": "{ \"logs\":[{\"uid\":1,\"ty\":3},CENSORED], \"dt\":[{\"uniqueId\":4,\"hp\":0,\"atk\":900}], \"hd\":\"AA==\", \"data\":\"AA==\" }",
  "raidResult": "[]",
  "superBossResult": "[]",
  "elapsedTurn": 5,
  "recordType": 1,
  "recordValueJson": {
    "turnMaxDamage": 0,
    "knockdownNum": 0,
    "totalDamageToAliveEnemy": 0
  },
  "tdPlayed": "[{\"userSvtId\":1,\"seqId\":CENSORED}]",
  "usedEquipSkillList": {},
  "aliveUniqueIds": [],
  "battleStatus": CENSORED,
  "voicePlayedList": "[[CENSORED,1],[CENSORED,4]]",
  "usedTurnList": [
    5
  ]
}

this contains the entire turn log, basically every little detail about what you did during the battle

the battleId is from the cache/replaced object in the battle setup request

battleStatus is generated by a rather complicated crc:

  uVar2 = BitConverter$$GetBytes
                    (uVar13 + param_5,
                     (int)userId + ((int)param_5 >> 0x1f) + (uint)CARRY4(uVar13,param_5),0);
  uVar4 = BitConverter$$GetBytes(uVar11 - 0x408fd5,local_44 - (uint)(uVar11 < 0x408fd5),0);
  uVar2 = FUN_015e2484(uVar2,uVar4,Method$Enumerable.Concat()_byte_);
  iVar5 = iVar15 + (uint)CARRY4(uVar12,-(iVar15 >> 0x1f));
  uVar4 = BitConverter$$GetBytes
                    ((uint)((byte)iVar5 & 1) << 0x1f | uVar12 + -(iVar15 >> 0x1f) >> 1,iVar5 >> 1,0)
  ;
  uVar2 = FUN_015e2484(uVar2,uVar4,Method$Enumerable.Concat()_byte_);
  uVar4 = BitConverter$$GetBytes(param_3 + 0x80000001,param_4 - (uint)(param_3 < 0x7fffffff),0);
  uVar2 = FUN_015e2484(uVar2,uVar4,Method$Enumerable.Concat()_byte_);
  uVar4 = BitConverter$$GetBytes(uVar10 - 0x25acf6,iVar14 - (uint)(uVar10 < 0x25acf6),0);
  uVar2 = FUN_015e2484(uVar2,uVar4,Method$Enumerable.Concat()_byte_);
  uVar2 = FUN_0174f6e4(uVar2,Method$Enumerable.ToArray()_byte_);
  if (((*(byte *)(Class$Crc32 + 0xbf) & 2) != 0) && (*(int *)(Class$Crc32 + 0x70) == 0)) {
    FUN_0035803c();
  }
  local_30 = Crc32$$Compute(uVar2,0);
  uVar2 = FUN_00381f7c(Class$uint,&local_30);
  if (iVar1 == 0) {
    throwNullPointer(0);
  }
  FUN_006eabe0(iVar1,"battleStatus",uVar2,Method$Dictionary_string_-object_.Add());

let's figure out where all those params come from first

let's track down param_5 . walking back the references we find BattleLogic$$connectBattleResult

  iVar16 = *(int *)(param_1 + 0x10);
  if (iVar16 == 0) {
    throwNullPointer(0);
  }
  uVar23 = *(undefined4 *)(iVar16 + 0x150);
  /* ... */
  BattleResultRequest$$beginRequest
            (iVar17,iVar17,uVar3,uVar2,uVar23,uVar1,pSVar22,uVar9,uVar10,uVar18,uVar11,uVar12,uVar24
             ,uVar25,uVar13,uVar14,uVar20,uVar15,0);

walking back 1 more reference

void BattleLogic$$procRetire(int param_1)

{
  int iVar1;
  
  iVar1 = *(int *)(param_1 + 0x10);
  if (iVar1 == 0) {
    throwNullPointer(0);
  }
  BattleData$$setResultRetire(iVar1,0);
  iVar1 = *(int *)(param_1 + 0x10);
  if (iVar1 == 0) {
    throwNullPointer(0);
  }
  BattleData$$SetUsedTurn(iVar1,0);
  BattleLogic$$connectBattleResult(param_1);
  return;
}

I can't find any references to this. I can speculate that it's part of the battle result struct though, and it's likely the timestamp like with userStatus. first i'm gonna investigate BattleLogic methods to see if we can figure out what's that stuct at 0x10

in BattleLogic$$UseSkill

  iVar4 = BattleData$$getPlayerServantData(iVar4,uVar3,0);
  if (iVar4 == 0) {
    iVar4 = *(int *)(param_1 + 0x10);
    if (iVar4 == 0) {
      throwNullPointer(0);
    }
    BattleData$$useMasterSkill(iVar4,iVar2,0);

ok, so it's the BattleData struct, let's search for its methods and see what's at 0x150 . also I used the rest of the code here to map out some fields in BattleLogic

wow this class has a looot of methods

found a reference to it, it seems to just be a bool that indicates victory? so maybe winResult in our result json

undefined4 BattleData$$getWinResult(BattleData *param_1)

{
  undefined4 uVar1;
  
  uVar1 = 0;
  if (*(int *)&param_1->field_0x150 == 1) {
    uVar1 = *(undefined4 *)&param_1->field_0x154;
  }
  return uVar1;
}

uVar11 comes from param_11

  while( true ) {
    if (param_11 == 0) {
      throwNullPointer(0);
      uVar13 = extraout_r1_00;
    }
    if ((int)*(uint *)(param_11 + 0xc) <= (int)uVar10) break;
    if (*(uint *)(param_11 + 0xc) <= uVar10) {
      uVar2 = IndexOutOfRangeException();
      throw(uVar2,0,0);
    }
    iVar14 = *(int *)(param_11 + 0x10 + uVar10 * 4);
    if (iVar14 == 0) {
      throwNullPointer(0);
    }
    uVar10 = uVar10 + 1;
    uVar12 = *(uint *)(iVar14 + 8);
    uVar13 = *(uint *)(iVar14 + 0xc);
    uVar3 = *(uint *)(iVar14 + 0x10);
    uVar6 = uVar11 + uVar3 + uVar13;
    uVar13 = local_44 + ((int)uVar3 >> 0x1f) + (uint)CARRY4(uVar11,uVar3) +
             ((int)uVar13 >> 0x1f) + (uint)CARRY4(uVar11 + uVar3,uVar13);
    uVar11 = uVar6 + uVar12;
    local_44 = uVar13 + ((int)uVar12 >> 0x1f) + (uint)CARRY4(uVar6,uVar12);
  }

it appears to be a list of RaidResult's

    if (*(char *)(iVar22 + 0x18b) != '\0') {
      uVar2 = BattleServantData$$getUniqueID(iVar22,0);
      iVar20 = *(int *)(param_1 + 0x10);
      uVar3 = BattleServantData$$getUniqueID(iVar22,0);
      if (iVar20 == 0) {
        throwNullPointer(0);
      }
      uVar3 = BattleData$$getRaidDay(iVar20,uVar3,0);
      iVar20 = BattleServantData$$getMaxHp(iVar22,0);
      iVar6 = BattleServantData$$getNowHp(iVar22,0);
      won = thunk_FUN_00382384(_Class$BattleResultRequest.RaidResult);
      BattleResultRequest.RaidResult$$.ctor(won,uVar2,uVar3,iVar20 - iVar6,0);
      if (iVar4 == 0) {
        throwNullPointer(0);
      }
      List$$Add(iVar4,won,_Method$List_BattleResultRequest.RaidResult_.Add());
    }

  /* .... */

  uVar11 = List$$ToArray(iVar4,Method$List_BattleResultRequest.RaidResult_.ToArray());
  if (iVar5 == 0) {
    throwNullPointer(0);
  }

so I'm guessing it's the raidResult field which is empty in our case

now we need to map RaidResult fields to figure out what it's using for the crc

we really don't need to reverse this right now as our raidList is empty but it might come in handy later

well, I couldn't find the names of the fields but it only seems to have 3 fields and they all get added together, so I guess we'll see when we get a raidResults array

the loop re-labeled:

  while( true ) {
    if (raidResult == (Array_RaidResult_ *)0x0) {
      throwNullPointer(0);
      uVar11 = extraout_r1_00;
    }
    if (raidResult->length <= (int)i) break;
    if ((uint)raidResult->length <= i) {
      uVar2 = IndexOutOfRangeException();
      throw(uVar2,0,0);
    }
    raidResultItem = raidResult->data[i];
    if (raidResultItem == (RaidResult *)0x0) {
      throwNullPointer(0);
    }
    i = i + 1;
    uVar10 = *(uint *)&raidResultItem->field_0x8;
    uVar11 = *(uint *)&raidResultItem->field_0xc;
    uVar3 = *(uint *)&raidResultItem->field_0x10;
    uVar6 = raidResultCrc + uVar3 + uVar11;
    uVar11 = local_44 + ((int)uVar3 >> 0x1f) + (uint)CARRY4(raidResultCrc,uVar3) +
             ((int)uVar11 >> 0x1f) + (uint)CARRY4(raidResultCrc + uVar3,uVar11);
    raidResultCrc = uVar6 + uVar10;
    local_44 = uVar11 + ((int)uVar10 >> 0x1f) + (uint)CARRY4(uVar6,uVar10);
  }

essentially it sums the 3 fields of every raid result

iVar13 is related to aliveUniqueIds which in our case is also empty

  FUN_006eabe0(iVar1,"aliveUniqueIds",param_10,Method$Dictionary_string_-object_.Add());
  uVar11 = 0;
  uVar10 = 0;
  iVar13 = 0;
  while( true ) {
    if (param_10 == (Array *)0x0) {
      throwNullPointer(0);
    }
    if (param_10->length <= (int)uVar11) break;
    if ((uint)param_10->length <= uVar11) {
      uVar2 = IndexOutOfRangeException();
      throw(uVar2,0,0);
    }
    uVar3 = *(uint *)(param_10->data + uVar11 * 4);
    uVar11 = uVar11 + 1;
    bVar14 = CARRY4(uVar10,uVar3);
    uVar10 = uVar10 + uVar3;
    iVar13 = iVar13 + ((int)uVar3 >> 0x1f) + (uint)bVar14;
  }

this seems to be a 64-bit add that got split into 2 variables by the compiler. it's probably just adding all the id's into one 64-bit value

then we have param_3 and param_4

  pBVar17 = param_1->battleData;
  if (pBVar17 == (BattleData *)0x0) {
    throwNullPointer(0);
  }
  iVar1 = BattleData$$getBattleEntity(pBVar17,0);

  /* ....... */

  uVar4 = *(undefined4 *)(iVar1 + 0x10);
  uVar18 = *(undefined4 *)(iVar1 + 0x14);

  /* ....... */

  BattleResultRequest$$beginRequest
            (iVar3,iVar3,uVar4,uVar18,winResult,uVar2,pSVar21,uVar22,uVar12,pAVar5,raidResult,uVar13
             ,uVar23,uVar24,uVar14,uVar15,uVar19,uVar16);

most likely another 64-bit integer split into two

yep, it seems to be some 64-bit integer called primarykey

String * BattleEntity$$getPrimarykey(BattleEntity *param_1)

{
  String *pSVar1;
  
  pSVar1 = (String *)FUN_01aabffc(&param_1->field_0x10,0);
  return pSVar1;
}

void FUN_01aabffc(undefined4 *param_1)

{
  undefined4 uVar1;
  undefined4 uVar2;
  
  if (DAT_027f406e == '\0') {
    FUN_003479b0(0x52bb);
    DAT_027f406e = '\x01';
  }
  uVar1 = *param_1;
  uVar2 = param_1[1];
  if (((*(byte *)(Class$System.NumberFormatter + 0xbf) & 2) != 0) &&
     (*(int *)(Class$System.NumberFormatter + 0x70) == 0)) {
    FUN_0035803c();
  }
  NumberFormatter$$NumberToString(uVar1,uVar2,0);
  return;
}

this could be battleId in our json, not 100% sure, but we can hook later to check if needed

variable i along with some other vars are reused earlier in the code to compute another crc component into i. very confusing to read, but just pretend those variables have no name. it comes from param_12

  i = 0;
  iVar11 = 0;
  while( true ) {
    if (param_12 == 0) {
      throwNullPointer(0,uVar10);
      uVar10 = extraout_r1_02;
    }
    if ((int)*(uint *)(param_12 + 0xc) <= (int)aliveUniqueIdsCrc1) break;
    if (*(uint *)(param_12 + 0xc) <= aliveUniqueIdsCrc1) {
      uVar2 = IndexOutOfRangeException();
      throw(uVar2,0,0);
      uVar10 = extraout_r1_01;
    }
    aliveUniqueIdsCrc2 = *(int *)(param_12 + 0x10 + aliveUniqueIdsCrc1 * 4);
    if (aliveUniqueIdsCrc2 == 0) {
      throwNullPointer(0,uVar10);
    }
    aliveUniqueIdsCrc1 = aliveUniqueIdsCrc1 + 1;
    uVar3 = *(uint *)(aliveUniqueIdsCrc2 + 8);
    uVar10 = *(uint *)(aliveUniqueIdsCrc2 + 0xc);
    uVar6 = *(uint *)(aliveUniqueIdsCrc2 + 0x10);
    bVar12 = CARRY4(i,uVar6);
    uVar7 = i + uVar6;
    uVar8 = uVar7 + uVar3;
    i = uVar8 + uVar10;
    iVar11 = iVar11 + ((int)uVar6 >> 0x1f) + (uint)bVar12 +
             ((int)uVar3 >> 0x1f) + (uint)CARRY4(uVar7,uVar3) +
             ((int)uVar10 >> 0x1f) + (uint)CARRY4(uVar8,uVar10);
  }

oops, my bad, I just noticed I was off by one when counting params, the raid result array is actually param_12 and the other one was param_11 which is an array of SuperBossResult which appears to be basically identical in layout

  uVar11 = List$$ToArray(iVar4,Method$List_BattleResultRequest.SuperBossResult_.ToArray());
  pBVar15 = param_1->battleData;
  if (pBVar15 == (BattleData *)0x0) {
    throwNullPointer(0);
    uVar20 = _DAT_0000005c;
    pBVar15 = param_1->battleData;
    if (pBVar15 == (BattleData *)0x0) {
      throwNullPointer(0);
      uVar21 = _DAT_00000084;
      pBVar15 = param_1->battleData;
      if (pBVar15 == (BattleData *)0x0) {
        throwNullPointer(0);
        pBVar15 = (BattleData *)0x0;
      }
      goto LAB_00a29584;
    }

oh what I thought was winResult was actually battleResult:

  local_34 = winResult;
  uVar2 = FUN_00381f7c(Class$int,&local_34);
  if (iVar1 == 0) {
    throwNullPointer(0);
  }
  FUN_006eabe0(iVar1,"battleResult",uVar2,Method$Dictionary_string_-object_.Add());

I just had to scroll up in BattleResultRequest$$beginRequest

finally, everything is named

  userId = NetworkManager$$get_UserId(0);
  uVar10 = (uint)((ulonglong)userId >> 0x20);
  if (((*(byte *)(Class$System.BitConverter + 0xbf) & 2) != 0) &&
     (*(int *)(Class$System.BitConverter + 0x70) == 0)) {
    FUN_0035803c();
  }
  crcData = BitConverter$$GetBytes
                      (uVar10 + battleResult,
                       (int)userId + ((int)battleResult >> 0x1f) + (uint)CARRY4(uVar10,battleResult)
                       ,0);
  uVar3 = BitConverter$$GetBytes
                    (raidResultCrc1 - 0x408fd5,raidResultCrc2 - (uint)(raidResultCrc1 < 0x408fd5),0)
  ;
  crcData = FUN_015e2484(crcData,uVar3,Method$Enumerable.Concat()_byte_);
  iVar4 = aliveUniqueIdsCrc2 + (uint)CARRY4(aliveUniqueIdsCrc1,-(aliveUniqueIdsCrc2 >> 0x1f));
  uVar3 = BitConverter$$GetBytes
                    ((uint)((byte)iVar4 & 1) << 0x1f |
                     aliveUniqueIdsCrc1 + -(aliveUniqueIdsCrc2 >> 0x1f) >> 1,iVar4 >> 1,0);
  crcData = FUN_015e2484(crcData,uVar3,Method$Enumerable.Concat()_byte_);
  uVar3 = BitConverter$$GetBytes
                    (battleEntityPrimaryKey1 + 0x80000001,
                     battleEntityPrimaryKey2 - (uint)(battleEntityPrimaryKey1 < 0x7fffffff),0);
  crcData = FUN_015e2484(crcData,uVar3,Method$Enumerable.Concat()_byte_);
  uVar3 = BitConverter$$GetBytes
                    (superBossCrc - 0x25acf6,superBossCrc2 - (uint)(superBossCrc < 0x25acf6),0);
  crcData = FUN_015e2484(crcData,uVar3,Method$Enumerable.Concat()_byte_);
  crcData = FUN_0174f6e4(crcData,Method$Enumerable.ToArray()_byte_);
  if (((*(byte *)(Class$Crc32 + 0xbf) & 2) != 0) && (*(int *)(Class$Crc32 + 0x70) == 0)) {
    FUN_0035803c();
  }
  battleStatus = Crc32$$Compute(crcData,0);
  crcData = FUN_00381f7c(Class$uint,&battleStatus);
  if (iVar1 == 0) {
    throwNullPointer(0);
  }
  FUN_006eabe0(iVar1,"battleStatus",crcData,Method$Dictionary_string_-object_.Add());

here's also the entire function with all parameters named if you wish to research battle data more: https://gist.github.com/Francesco149/6287a96740fb6dab44983aedf7f5e88c

to be continued...

About

information about fate grand order internals

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published