| File: | Source/Core/Core/Src/HW/GCMemcard.cpp |
| Location: | line 186, column 4 |
| Description: | Value stored to 'csums' is never read |
| 1 | // Copyright 2013 Dolphin Emulator Project |
| 2 | // Licensed under GPLv2 |
| 3 | // Refer to the license.txt file included. |
| 4 | |
| 5 | #include "GCMemcard.h" |
| 6 | #include "ColorUtil.h" |
| 7 | static void ByteSwap(u8 *valueA, u8 *valueB) |
| 8 | { |
| 9 | u8 tmp = *valueA; |
| 10 | *valueA = *valueB; |
| 11 | *valueB = tmp; |
| 12 | } |
| 13 | |
| 14 | void decode5A3image(u32* dst, u16* src, int width, int height) |
| 15 | { |
| 16 | for (int y = 0; y < height; y += 4) |
| 17 | { |
| 18 | for (int x = 0; x < width; x += 4) |
| 19 | { |
| 20 | for (int iy = 0; iy < 4; iy++, src += 4) |
| 21 | { |
| 22 | for (int ix = 0; ix < 4; ix++) |
| 23 | { |
| 24 | u32 RGBA = ColorUtil::Decode5A3(Common::swap16(src[ix])); |
| 25 | dst[(y + iy) * width + (x + ix)] = RGBA; |
| 26 | } |
| 27 | } |
| 28 | } |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | void decodeCI8image(u32* dst, u8* src, u16* pal, int width, int height) |
| 33 | { |
| 34 | for (int y = 0; y < height; y += 4) |
| 35 | { |
| 36 | for (int x = 0; x < width; x += 8) |
| 37 | { |
| 38 | for (int iy = 0; iy < 4; iy++, src += 8) |
| 39 | { |
| 40 | u32 *tdst = dst+(y+iy)*width+x; |
| 41 | for (int ix = 0; ix < 8; ix++) |
| 42 | { |
| 43 | // huh, this seems wrong. CI8, not 5A3, no? |
| 44 | tdst[ix] = ColorUtil::Decode5A3(Common::swap16(pal[src[ix]])); |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | GCMemcard::GCMemcard(const char *filename, bool forceCreation, bool sjis) |
| 52 | : m_valid(false) |
| 53 | , m_fileName(filename) |
| 54 | { |
| 55 | // Currently there is a string freeze. instead of adding a new message about needing r/w |
| 56 | // open file read only, if write is denied the error will be reported at that point |
| 57 | File::IOFile mcdFile(m_fileName, "rb"); |
| 58 | if (!mcdFile.IsOpen()) |
| 59 | { |
| 60 | if (!forceCreation && !AskYesNoT("\"%s\" does not exist.\n Create a new 16MB Memcard?", filename)MsgAlert(true, QUESTION, "\"%s\" does not exist.\n Create a new 16MB Memcard?" , filename)) |
| 61 | { |
| 62 | return; |
| 63 | } |
| 64 | Format(forceCreation ? sjis : !AskYesNoT("Format as ascii (NTSC\\PAL)?\nChoose no for sjis (NTSC-J)")MsgAlert(true, QUESTION, "Format as ascii (NTSC\\PAL)?\nChoose no for sjis (NTSC-J)" )); |
| 65 | return; |
| 66 | } |
| 67 | else |
| 68 | { |
| 69 | //This function can be removed once more about hdr is known and we can check for a valid header |
| 70 | std::string fileType; |
| 71 | SplitPath(filename, NULL__null, NULL__null, &fileType); |
| 72 | if (strcasecmp(fileType.c_str(), ".raw") && strcasecmp(fileType.c_str(), ".gcp")) |
| 73 | { |
| 74 | PanicAlertT("File has the extension \"%s\"\nvalid extensions are (.raw/.gcp)", fileType.c_str())MsgAlert(false, WARNING, "File has the extension \"%s\"\nvalid extensions are (.raw/.gcp)" , fileType.c_str()); |
| 75 | return; |
| 76 | } |
| 77 | u32 size = mcdFile.GetSize(); |
| 78 | if (size < MC_FST_BLOCKS*BLOCK_SIZE) |
| 79 | { |
| 80 | PanicAlertT("%s failed to load as a memorycard \nfile is not large enough to be a valid memory card file (0x%x bytes)", filename, size)MsgAlert(false, WARNING, "%s failed to load as a memorycard \nfile is not large enough to be a valid memory card file (0x%x bytes)" , filename, size); |
| 81 | return; |
| 82 | } |
| 83 | if (size % BLOCK_SIZE) |
| 84 | { |
| 85 | PanicAlertT("%s failed to load as a memorycard \n Card file size is invalid (0x%x bytes)", filename, size)MsgAlert(false, WARNING, "%s failed to load as a memorycard \n Card file size is invalid (0x%x bytes)" , filename, size); |
| 86 | return; |
| 87 | } |
| 88 | |
| 89 | m_sizeMb = (size/BLOCK_SIZE) / MBIT_TO_BLOCKS; |
| 90 | switch (m_sizeMb) |
| 91 | { |
| 92 | case MemCard59Mb: |
| 93 | case MemCard123Mb: |
| 94 | case MemCard251Mb: |
| 95 | case Memcard507Mb: |
| 96 | case MemCard1019Mb: |
| 97 | case MemCard2043Mb: |
| 98 | break; |
| 99 | default: |
| 100 | PanicAlertT("%s failed to load as a memorycard \n Card size is invalid (0x%x bytes)", filename, size)MsgAlert(false, WARNING, "%s failed to load as a memorycard \n Card size is invalid (0x%x bytes)" , filename, size); |
| 101 | return; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | |
| 106 | mcdFile.Seek(0, SEEK_SET0); |
| 107 | if (!mcdFile.ReadBytes(&hdr, BLOCK_SIZE)) |
| 108 | { |
| 109 | PanicAlertT("Failed to read header correctly\n(0x0000-0x1FFF)")MsgAlert(false, WARNING, "Failed to read header correctly\n(0x0000-0x1FFF)" ); |
| 110 | return; |
| 111 | } |
| 112 | if (m_sizeMb != BE16(hdr.SizeMb)(Common::swap16(hdr.SizeMb))) |
| 113 | { |
| 114 | PanicAlertT("Memorycard filesize does not match the header size")MsgAlert(false, WARNING, "Memorycard filesize does not match the header size" ); |
| 115 | return; |
| 116 | } |
| 117 | |
| 118 | if (!mcdFile.ReadBytes(&dir, BLOCK_SIZE)) |
| 119 | { |
| 120 | PanicAlertT("Failed to read directory correctly\n(0x2000-0x3FFF)")MsgAlert(false, WARNING, "Failed to read directory correctly\n(0x2000-0x3FFF)" ); |
| 121 | return; |
| 122 | } |
| 123 | |
| 124 | if (!mcdFile.ReadBytes(&dir_backup, BLOCK_SIZE)) |
| 125 | { |
| 126 | PanicAlertT("Failed to read directory backup correctly\n(0x4000-0x5FFF)")MsgAlert(false, WARNING, "Failed to read directory backup correctly\n(0x4000-0x5FFF)" ); |
| 127 | return; |
| 128 | } |
| 129 | |
| 130 | if (!mcdFile.ReadBytes(&bat, BLOCK_SIZE)) |
| 131 | { |
| 132 | PanicAlertT("Failed to read block allocation table correctly\n(0x6000-0x7FFF)")MsgAlert(false, WARNING, "Failed to read block allocation table correctly\n(0x6000-0x7FFF)" ); |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | if (!mcdFile.ReadBytes(&bat_backup, BLOCK_SIZE)) |
| 137 | { |
| 138 | PanicAlertT("Failed to read block allocation table backup correctly\n(0x8000-0x9FFF)")MsgAlert(false, WARNING, "Failed to read block allocation table backup correctly\n(0x8000-0x9FFF)" ); |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | u32 csums = TestChecksums(); |
| 143 | |
| 144 | if (csums & 0x1) |
| 145 | { |
| 146 | // header checksum error! |
| 147 | // invalid files do not always get here |
| 148 | PanicAlertT("Header checksum failed")MsgAlert(false, WARNING, "Header checksum failed"); |
| 149 | return; |
| 150 | } |
| 151 | |
| 152 | if (csums & 0x2) // directory checksum error! |
| 153 | { |
| 154 | if (csums & 0x4) |
| 155 | { |
| 156 | // backup is also wrong! |
| 157 | PanicAlertT("Directory checksum failed\n and Directory backup checksum failed")MsgAlert(false, WARNING, "Directory checksum failed\n and Directory backup checksum failed" ); |
| 158 | return; |
| 159 | } |
| 160 | else |
| 161 | { |
| 162 | // backup is correct, restore |
| 163 | dir = dir_backup; |
| 164 | bat = bat_backup; |
| 165 | |
| 166 | // update checksums |
| 167 | csums = TestChecksums(); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | if (csums & 0x8) // BAT checksum error! |
| 172 | { |
| 173 | if (csums & 0x10) |
| 174 | { |
| 175 | // backup is also wrong! |
| 176 | PanicAlertT("Block Allocation Table checksum failed")MsgAlert(false, WARNING, "Block Allocation Table checksum failed" ); |
| 177 | return; |
| 178 | } |
| 179 | else |
| 180 | { |
| 181 | // backup is correct, restore |
| 182 | dir = dir_backup; |
| 183 | bat = bat_backup; |
| 184 | |
| 185 | // update checksums |
| 186 | csums = TestChecksums(); |
Value stored to 'csums' is never read | |
| 187 | } |
| 188 | // It seems that the backup having a larger counter doesn't necessarily mean |
| 189 | // the backup should be copied? |
| 190 | // } |
| 191 | // |
| 192 | // if(BE16(dir_backup.UpdateCounter) > BE16(dir.UpdateCounter)) //check if the backup is newer |
| 193 | // { |
| 194 | // dir = dir_backup; |
| 195 | // bat = bat_backup; // needed? |
| 196 | } |
| 197 | |
| 198 | mcdFile.Seek(0xa000, SEEK_SET0); |
| 199 | |
| 200 | maxBlock = (u32)m_sizeMb * MBIT_TO_BLOCKS; |
| 201 | mc_data_blocks.reserve(maxBlock - MC_FST_BLOCKS); |
| 202 | |
| 203 | m_valid = true; |
| 204 | for (u32 i = MC_FST_BLOCKS; i < maxBlock; ++i) |
| 205 | { |
| 206 | GCMBlock b; |
| 207 | if (mcdFile.ReadBytes(b.block, BLOCK_SIZE)) |
| 208 | { |
| 209 | mc_data_blocks.push_back(b); |
| 210 | } |
| 211 | else |
| 212 | { |
| 213 | PanicAlertT("Failed to read block %d of the save data\nMemcard may be truncated\nFilePosition:%llx", i, mcdFile.Tell())MsgAlert(false, WARNING, "Failed to read block %d of the save data\nMemcard may be truncated\nFilePosition:%llx" , i, mcdFile.Tell()); |
| 214 | m_valid = false; |
| 215 | break; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | mcdFile.Close(); |
| 220 | |
| 221 | initDirBatPointers(); |
| 222 | } |
| 223 | |
| 224 | void GCMemcard::initDirBatPointers() |
| 225 | { |
| 226 | if (BE16(dir.UpdateCounter)(Common::swap16(dir.UpdateCounter)) > (BE16(dir_backup.UpdateCounter)(Common::swap16(dir_backup.UpdateCounter)))) |
| 227 | { |
| 228 | CurrentDir = &dir; |
| 229 | PreviousDir = &dir_backup; |
| 230 | } |
| 231 | else |
| 232 | { |
| 233 | CurrentDir = &dir_backup; |
| 234 | PreviousDir = &dir; |
| 235 | } |
| 236 | if (BE16(bat.UpdateCounter)(Common::swap16(bat.UpdateCounter)) > BE16(bat_backup.UpdateCounter)(Common::swap16(bat_backup.UpdateCounter))) |
| 237 | { |
| 238 | CurrentBat = &bat; |
| 239 | PreviousBat = &bat_backup; |
| 240 | } |
| 241 | else |
| 242 | { |
| 243 | CurrentBat = &bat_backup; |
| 244 | PreviousBat = &bat; |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | bool GCMemcard::IsAsciiEncoding() const |
| 249 | { |
| 250 | return hdr.Encoding == 0; |
| 251 | } |
| 252 | |
| 253 | bool GCMemcard::Save() |
| 254 | { |
| 255 | File::IOFile mcdFile(m_fileName, "wb"); |
| 256 | mcdFile.Seek(0, SEEK_SET0); |
| 257 | |
| 258 | mcdFile.WriteBytes(&hdr, BLOCK_SIZE); |
| 259 | mcdFile.WriteBytes(&dir, BLOCK_SIZE); |
| 260 | mcdFile.WriteBytes(&dir_backup, BLOCK_SIZE); |
| 261 | mcdFile.WriteBytes(&bat, BLOCK_SIZE); |
| 262 | mcdFile.WriteBytes(&bat_backup, BLOCK_SIZE); |
| 263 | for (unsigned int i = 0; i < maxBlock - MC_FST_BLOCKS; ++i) |
| 264 | { |
| 265 | mcdFile.WriteBytes(mc_data_blocks[i].block, BLOCK_SIZE); |
| 266 | } |
| 267 | |
| 268 | return mcdFile.Close(); |
| 269 | } |
| 270 | |
| 271 | void GCMemcard::calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum) |
| 272 | { |
| 273 | *csum = *inv_csum = 0; |
| 274 | |
| 275 | for (u32 i = 0; i < length; ++i) |
| 276 | { |
| 277 | //weird warnings here |
| 278 | *csum += BE16(buf[i])(Common::swap16(buf[i])); |
| 279 | *inv_csum += BE16((u16)(buf[i] ^ 0xffff))(Common::swap16((u16)(buf[i] ^ 0xffff))); |
| 280 | } |
| 281 | *csum = BE16(*csum)(Common::swap16(*csum)); |
| 282 | *inv_csum = BE16(*inv_csum)(Common::swap16(*inv_csum)); |
| 283 | if (*csum == 0xffff) |
| 284 | { |
| 285 | *csum = 0; |
| 286 | } |
| 287 | if (*inv_csum == 0xffff) |
| 288 | { |
| 289 | *inv_csum = 0; |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | u32 GCMemcard::TestChecksums() const |
| 294 | { |
| 295 | u16 csum=0, |
| 296 | csum_inv=0; |
| 297 | |
| 298 | u32 results = 0; |
| 299 | |
| 300 | calc_checksumsBE((u16*)&hdr, 0xFE , &csum, &csum_inv); |
| 301 | if ((hdr.Checksum != csum) || (hdr.Checksum_Inv != csum_inv)) |
| 302 | results |= 1; |
| 303 | |
| 304 | calc_checksumsBE((u16*)&dir, 0xFFE, &csum, &csum_inv); |
| 305 | if ((dir.Checksum != csum) || (dir.Checksum_Inv != csum_inv)) |
| 306 | results |= 2; |
| 307 | |
| 308 | calc_checksumsBE((u16*)&dir_backup, 0xFFE, &csum, &csum_inv); |
| 309 | if ((dir_backup.Checksum != csum) || (dir_backup.Checksum_Inv != csum_inv)) |
| 310 | results |= 4; |
| 311 | |
| 312 | calc_checksumsBE((u16*)(((u8*)&bat)+4), 0xFFE, &csum, &csum_inv); |
| 313 | if ((bat.Checksum != csum) || (bat.Checksum_Inv != csum_inv)) |
| 314 | results |= 8; |
| 315 | |
| 316 | calc_checksumsBE((u16*)(((u8*)&bat_backup)+4), 0xFFE, &csum, &csum_inv); |
| 317 | if ((bat_backup.Checksum != csum) || (bat_backup.Checksum_Inv != csum_inv)) |
| 318 | results |= 16; |
| 319 | |
| 320 | return results; |
| 321 | } |
| 322 | |
| 323 | bool GCMemcard::FixChecksums() |
| 324 | { |
| 325 | if (!m_valid) |
| 326 | return false; |
| 327 | |
| 328 | calc_checksumsBE((u16*)&hdr, 0xFE, &hdr.Checksum, &hdr.Checksum_Inv); |
| 329 | calc_checksumsBE((u16*)&dir, 0xFFE, &dir.Checksum, &dir.Checksum_Inv); |
| 330 | calc_checksumsBE((u16*)&dir_backup, 0xFFE, &dir_backup.Checksum, &dir_backup.Checksum_Inv); |
| 331 | calc_checksumsBE((u16*)&bat+2, 0xFFE, &bat.Checksum, &bat.Checksum_Inv); |
| 332 | calc_checksumsBE((u16*)&bat_backup+2, 0xFFE, &bat_backup.Checksum, &bat_backup.Checksum_Inv); |
| 333 | |
| 334 | return true; |
| 335 | } |
| 336 | |
| 337 | u8 GCMemcard::GetNumFiles() const |
| 338 | { |
| 339 | if (!m_valid) |
| 340 | return 0; |
| 341 | |
| 342 | u8 j = 0; |
| 343 | for (int i = 0; i < DIRLEN; i++) |
| 344 | { |
| 345 | if (BE32(CurrentDir->Dir[i].Gamecode)(Common::swap32(CurrentDir->Dir[i].Gamecode))!= 0xFFFFFFFF) |
| 346 | j++; |
| 347 | } |
| 348 | return j; |
| 349 | } |
| 350 | |
| 351 | u8 GCMemcard::GetFileIndex(u8 fileNumber) const |
| 352 | { |
| 353 | if (m_valid) |
| 354 | { |
| 355 | u8 j = 0; |
| 356 | for (u8 i = 0; i < DIRLEN; i++) |
| 357 | { |
| 358 | if (BE32(CurrentDir->Dir[i].Gamecode)(Common::swap32(CurrentDir->Dir[i].Gamecode))!= 0xFFFFFFFF) |
| 359 | { |
| 360 | if (j == fileNumber) |
| 361 | { |
| 362 | return i; |
| 363 | } |
| 364 | j++; |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | return 0xFF; |
| 369 | } |
| 370 | |
| 371 | u16 GCMemcard::GetFreeBlocks() const |
| 372 | { |
| 373 | if (!m_valid) |
| 374 | return 0; |
| 375 | |
| 376 | return BE16(CurrentBat->FreeBlocks)(Common::swap16(CurrentBat->FreeBlocks)); |
| 377 | } |
| 378 | |
| 379 | u8 GCMemcard::TitlePresent(DEntry d) const |
| 380 | { |
| 381 | if (!m_valid) |
| 382 | return DIRLEN; |
| 383 | |
| 384 | u8 i = 0; |
| 385 | while(i < DIRLEN) |
| 386 | { |
| 387 | if ((BE32(CurrentDir->Dir[i].Gamecode)(Common::swap32(CurrentDir->Dir[i].Gamecode)) == BE32(d.Gamecode)(Common::swap32(d.Gamecode))) && |
| 388 | (!memcmp(CurrentDir->Dir[i].Filename, d.Filename, 32))) |
| 389 | { |
| 390 | break; |
| 391 | } |
| 392 | i++; |
| 393 | } |
| 394 | return i; |
| 395 | } |
| 396 | |
| 397 | bool GCMemcard::GCI_FileName(u8 index, std::string &filename) const |
| 398 | { |
| 399 | if (!m_valid || index > DIRLEN || (BE32(CurrentDir->Dir[index].Gamecode)(Common::swap32(CurrentDir->Dir[index].Gamecode)) == 0xFFFFFFFF)) |
| 400 | return false; |
| 401 | |
| 402 | filename = std::string((char*)CurrentDir->Dir[index].Gamecode, 4) + '_' + (char*)CurrentDir->Dir[index].Filename + ".gci"; |
| 403 | return true; |
| 404 | } |
| 405 | |
| 406 | // DEntry functions, all take u8 index < DIRLEN (127) |
| 407 | // Functions that have ascii output take a char *buffer |
| 408 | |
| 409 | std::string GCMemcard::DEntry_GameCode(u8 index) const |
| 410 | { |
| 411 | if (!m_valid || index > DIRLEN) |
| 412 | return ""; |
| 413 | |
| 414 | return std::string((const char*)CurrentDir->Dir[index].Gamecode, 4); |
| 415 | } |
| 416 | |
| 417 | std::string GCMemcard::DEntry_Makercode(u8 index) const |
| 418 | { |
| 419 | if (!m_valid || index > DIRLEN) |
| 420 | return ""; |
| 421 | return std::string((const char*)CurrentDir->Dir[index].Makercode, 2); |
| 422 | } |
| 423 | |
| 424 | std::string GCMemcard::DEntry_BIFlags(u8 index) const |
| 425 | { |
| 426 | if (!m_valid || index > DIRLEN) |
| 427 | return ""; |
| 428 | |
| 429 | std::string flags; |
| 430 | int x = CurrentDir->Dir[index].BIFlags; |
| 431 | for (int i = 0; i < 8; i++) |
| 432 | { |
| 433 | flags.push_back((x & 0x80) ? '1' : '0'); |
| 434 | x = x << 1; |
| 435 | } |
| 436 | return flags; |
| 437 | } |
| 438 | |
| 439 | std::string GCMemcard::DEntry_FileName(u8 index) const |
| 440 | { |
| 441 | if (!m_valid || index > DIRLEN) |
| 442 | return ""; |
| 443 | |
| 444 | return std::string((const char*)CurrentDir->Dir[index].Filename, DENTRY_STRLEN); |
| 445 | } |
| 446 | |
| 447 | u32 GCMemcard::DEntry_ModTime(u8 index) const |
| 448 | { |
| 449 | if (!m_valid || index > DIRLEN) |
| 450 | return 0xFFFFFFFF; |
| 451 | |
| 452 | return BE32(CurrentDir->Dir[index].ModTime)(Common::swap32(CurrentDir->Dir[index].ModTime)); |
| 453 | } |
| 454 | |
| 455 | u32 GCMemcard::DEntry_ImageOffset(u8 index) const |
| 456 | { |
| 457 | if (!m_valid || index > DIRLEN) |
| 458 | return 0xFFFFFFFF; |
| 459 | |
| 460 | return BE32(CurrentDir->Dir[index].ImageOffset)(Common::swap32(CurrentDir->Dir[index].ImageOffset)); |
| 461 | } |
| 462 | |
| 463 | std::string GCMemcard::DEntry_IconFmt(u8 index) const |
| 464 | { |
| 465 | if (!m_valid || index > DIRLEN) |
| 466 | return ""; |
| 467 | |
| 468 | int x = CurrentDir->Dir[index].IconFmt[0]; |
| 469 | std::string format; |
| 470 | for(int i = 0; i < 16; i++) |
| 471 | { |
| 472 | if (i == 8) x = CurrentDir->Dir[index].IconFmt[1]; |
| 473 | format.push_back((x & 0x80) ? '1' : '0'); |
| 474 | x = x << 1; |
| 475 | } |
| 476 | return format; |
| 477 | } |
| 478 | |
| 479 | std::string GCMemcard::DEntry_AnimSpeed(u8 index) const |
| 480 | { |
| 481 | if (!m_valid || index > DIRLEN) |
| 482 | return ""; |
| 483 | |
| 484 | int x = CurrentDir->Dir[index].AnimSpeed[0]; |
| 485 | std::string speed; |
| 486 | for(int i = 0; i < 16; i++) |
| 487 | { |
| 488 | if (i == 8) x = CurrentDir->Dir[index].AnimSpeed[1]; |
| 489 | speed.push_back((x & 0x80) ? '1' : '0'); |
| 490 | x = x << 1; |
| 491 | } |
| 492 | return speed; |
| 493 | } |
| 494 | |
| 495 | std::string GCMemcard::DEntry_Permissions(u8 index) const |
| 496 | { |
| 497 | if (!m_valid || index > DIRLEN) |
| 498 | return ""; |
| 499 | |
| 500 | u8 Permissions = CurrentDir->Dir[index].Permissions; |
| 501 | std::string permissionsString; |
| 502 | permissionsString.push_back((Permissions & 16) ? 'x' : 'M'); |
| 503 | permissionsString.push_back((Permissions & 8) ? 'x' : 'C'); |
| 504 | permissionsString.push_back((Permissions & 4) ? 'P' : 'x'); |
| 505 | return permissionsString; |
| 506 | } |
| 507 | |
| 508 | u8 GCMemcard::DEntry_CopyCounter(u8 index) const |
| 509 | { |
| 510 | if (!m_valid || index > DIRLEN) |
| 511 | return 0xFF; |
| 512 | |
| 513 | return CurrentDir->Dir[index].CopyCounter; |
| 514 | } |
| 515 | |
| 516 | u16 GCMemcard::DEntry_FirstBlock(u8 index) const |
| 517 | { |
| 518 | if (!m_valid || index > DIRLEN) |
| 519 | return 0xFFFF; |
| 520 | |
| 521 | u16 block = BE16(CurrentDir->Dir[index].FirstBlock)(Common::swap16(CurrentDir->Dir[index].FirstBlock)); |
| 522 | if (block > (u16) maxBlock) return 0xFFFF; |
| 523 | return block; |
| 524 | } |
| 525 | |
| 526 | u16 GCMemcard::DEntry_BlockCount(u8 index) const |
| 527 | { |
| 528 | if (!m_valid || index > DIRLEN) |
| 529 | return 0xFFFF; |
| 530 | |
| 531 | u16 blocks = BE16(CurrentDir->Dir[index].BlockCount)(Common::swap16(CurrentDir->Dir[index].BlockCount)); |
| 532 | if (blocks > (u16) maxBlock) return 0xFFFF; |
| 533 | return blocks; |
| 534 | } |
| 535 | |
| 536 | u32 GCMemcard::DEntry_CommentsAddress(u8 index) const |
| 537 | { |
| 538 | if (!m_valid || index > DIRLEN) |
| 539 | return 0xFFFF; |
| 540 | |
| 541 | return BE32(CurrentDir->Dir[index].CommentsAddr)(Common::swap32(CurrentDir->Dir[index].CommentsAddr)); |
| 542 | } |
| 543 | |
| 544 | std::string GCMemcard::GetSaveComment1(u8 index) const |
| 545 | { |
| 546 | if (!m_valid || index > DIRLEN) |
| 547 | return ""; |
| 548 | |
| 549 | u32 Comment1 = BE32(CurrentDir->Dir[index].CommentsAddr)(Common::swap32(CurrentDir->Dir[index].CommentsAddr)); |
| 550 | u32 DataBlock = BE16(CurrentDir->Dir[index].FirstBlock)(Common::swap16(CurrentDir->Dir[index].FirstBlock)) - MC_FST_BLOCKS; |
| 551 | if ((DataBlock > maxBlock) || (Comment1 == 0xFFFFFFFF)) |
| 552 | { |
| 553 | return ""; |
| 554 | } |
| 555 | return std::string((const char *)mc_data_blocks[DataBlock].block + Comment1, DENTRY_STRLEN); |
| 556 | } |
| 557 | |
| 558 | std::string GCMemcard::GetSaveComment2(u8 index) const |
| 559 | { |
| 560 | if (!m_valid || index > DIRLEN) |
| 561 | return ""; |
| 562 | |
| 563 | u32 Comment1 = BE32(CurrentDir->Dir[index].CommentsAddr)(Common::swap32(CurrentDir->Dir[index].CommentsAddr)); |
| 564 | u32 Comment2 = Comment1 + DENTRY_STRLEN; |
| 565 | u32 DataBlock = BE16(CurrentDir->Dir[index].FirstBlock)(Common::swap16(CurrentDir->Dir[index].FirstBlock)) - MC_FST_BLOCKS; |
| 566 | if ((DataBlock > maxBlock) || (Comment1 == 0xFFFFFFFF)) |
| 567 | { |
| 568 | return ""; |
| 569 | } |
| 570 | return std::string((const char *)mc_data_blocks[DataBlock].block + Comment2, DENTRY_STRLEN); |
| 571 | } |
| 572 | |
| 573 | bool GCMemcard::GetDEntry(u8 index, DEntry &dest) const |
| 574 | { |
| 575 | if (!m_valid || index > DIRLEN) |
| 576 | return false; |
| 577 | |
| 578 | dest = CurrentDir->Dir[index]; |
| 579 | return true; |
| 580 | } |
| 581 | |
| 582 | u16 GCMemcard::BlockAlloc::GetNextBlock(u16 Block) const |
| 583 | { |
| 584 | if ((Block < MC_FST_BLOCKS) || (Block > 4091)) |
| 585 | return 0; |
| 586 | |
| 587 | return Common::swap16(Map[Block-MC_FST_BLOCKS]); |
| 588 | } |
| 589 | |
| 590 | u16 GCMemcard::BlockAlloc::NextFreeBlock(u16 StartingBlock) const |
| 591 | { |
| 592 | if (FreeBlocks) |
| 593 | { |
| 594 | for (u16 i = StartingBlock; i < BAT_SIZE; ++i) |
| 595 | if (Map[i-MC_FST_BLOCKS] == 0) |
| 596 | return i; |
| 597 | |
| 598 | for (u16 i = MC_FST_BLOCKS; i < StartingBlock; ++i) |
| 599 | if (Map[i-MC_FST_BLOCKS] == 0) |
| 600 | return i; |
| 601 | } |
| 602 | return 0xFFFF; |
| 603 | } |
| 604 | |
| 605 | bool GCMemcard::BlockAlloc::ClearBlocks(u16 FirstBlock, u16 BlockCount) |
| 606 | { |
| 607 | std::vector<u16> blocks; |
| 608 | while (FirstBlock != 0xFFFF && FirstBlock != 0) |
| 609 | { |
| 610 | blocks.push_back(FirstBlock); |
| 611 | FirstBlock = GetNextBlock(FirstBlock); |
| 612 | } |
| 613 | if (FirstBlock > 0) |
| 614 | { |
| 615 | size_t length = blocks.size(); |
| 616 | if (length != BlockCount) |
| 617 | { |
| 618 | return false; |
| 619 | } |
| 620 | for (unsigned int i = 0; i < length; ++i) |
| 621 | Map[blocks.at(i)-MC_FST_BLOCKS] = 0; |
| 622 | FreeBlocks = BE16(BE16(FreeBlocks) + BlockCount)(Common::swap16((Common::swap16(FreeBlocks)) + BlockCount)); |
| 623 | |
| 624 | return true; |
| 625 | } |
| 626 | return false; |
| 627 | } |
| 628 | |
| 629 | u32 GCMemcard::GetSaveData(u8 index, std::vector<GCMBlock> & Blocks) const |
| 630 | { |
| 631 | if (!m_valid) |
| 632 | return NOMEMCARD; |
| 633 | |
| 634 | u16 block = DEntry_FirstBlock(index); |
| 635 | u16 BlockCount = DEntry_BlockCount(index); |
| 636 | //u16 memcardSize = BE16(hdr.SizeMb) * MBIT_TO_BLOCKS; |
| 637 | |
| 638 | if ((block == 0xFFFF) || (BlockCount == 0xFFFF)) |
| 639 | { |
| 640 | return FAIL; |
| 641 | } |
| 642 | |
| 643 | u16 nextBlock = block; |
| 644 | for (int i = 0; i < BlockCount; ++i) |
| 645 | { |
| 646 | if ((!nextBlock) || (nextBlock == 0xFFFF)) |
| 647 | return FAIL; |
| 648 | Blocks.push_back(mc_data_blocks[nextBlock-MC_FST_BLOCKS]); |
| 649 | nextBlock = CurrentBat->GetNextBlock(nextBlock); |
| 650 | } |
| 651 | return SUCCESS; |
| 652 | } |
| 653 | // End DEntry functions |
| 654 | |
| 655 | u32 GCMemcard::ImportFile(DEntry& direntry, std::vector<GCMBlock> &saveBlocks) |
| 656 | { |
| 657 | if (!m_valid) |
| 658 | return NOMEMCARD; |
| 659 | |
| 660 | if (GetNumFiles() >= DIRLEN) |
| 661 | { |
| 662 | return OUTOFDIRENTRIES; |
| 663 | } |
| 664 | if (BE16(CurrentBat->FreeBlocks)(Common::swap16(CurrentBat->FreeBlocks)) < BE16(direntry.BlockCount)(Common::swap16(direntry.BlockCount))) |
| 665 | { |
| 666 | return OUTOFBLOCKS; |
| 667 | } |
| 668 | if (TitlePresent(direntry) != DIRLEN) |
| 669 | { |
| 670 | return TITLEPRESENT; |
| 671 | } |
| 672 | |
| 673 | // find first free data block |
| 674 | u16 firstBlock = CurrentBat->NextFreeBlock(BE16(CurrentBat->LastAllocated)(Common::swap16(CurrentBat->LastAllocated))); |
| 675 | if (firstBlock == 0xFFFF) |
| 676 | return OUTOFBLOCKS; |
| 677 | Directory UpdatedDir = *CurrentDir; |
| 678 | |
| 679 | // find first free dir entry |
| 680 | for (int i=0; i < DIRLEN; i++) |
| 681 | { |
| 682 | if (BE32(UpdatedDir.Dir[i].Gamecode)(Common::swap32(UpdatedDir.Dir[i].Gamecode)) == 0xFFFFFFFF) |
| 683 | { |
| 684 | UpdatedDir.Dir[i] = direntry; |
| 685 | *(u16*)&UpdatedDir.Dir[i].FirstBlock = BE16(firstBlock)(Common::swap16(firstBlock)); |
| 686 | UpdatedDir.Dir[i].CopyCounter = UpdatedDir.Dir[i].CopyCounter+1; |
| 687 | break; |
| 688 | } |
| 689 | } |
| 690 | UpdatedDir.UpdateCounter = BE16(BE16(UpdatedDir.UpdateCounter) + 1)(Common::swap16((Common::swap16(UpdatedDir.UpdateCounter)) + 1 )); |
| 691 | *PreviousDir = UpdatedDir; |
| 692 | if (PreviousDir == &dir ) |
| 693 | { |
| 694 | CurrentDir = &dir; |
| 695 | PreviousDir = &dir_backup; |
| 696 | } |
| 697 | else |
| 698 | { |
| 699 | CurrentDir = &dir_backup; |
| 700 | PreviousDir = &dir; |
| 701 | } |
| 702 | |
| 703 | int fileBlocks = BE16(direntry.BlockCount)(Common::swap16(direntry.BlockCount)); |
| 704 | |
| 705 | FZEROGX_MakeSaveGameValid(direntry, saveBlocks); |
| 706 | PSO_MakeSaveGameValid(direntry, saveBlocks); |
| 707 | |
| 708 | BlockAlloc UpdatedBat = *CurrentBat; |
| 709 | u16 nextBlock; |
| 710 | // keep assuming no freespace fragmentation, and copy over all the data |
| 711 | for (int i = 0; i < fileBlocks; ++i) |
| 712 | { |
| 713 | if (firstBlock == 0xFFFF) |
| 714 | PanicAlert("Fatal Error")MsgAlert(false, WARNING, "Fatal Error"); |
| 715 | mc_data_blocks[firstBlock - MC_FST_BLOCKS] = saveBlocks[i]; |
| 716 | if (i == fileBlocks-1) |
| 717 | nextBlock = 0xFFFF; |
| 718 | else |
| 719 | nextBlock = UpdatedBat.NextFreeBlock(firstBlock+1); |
| 720 | UpdatedBat.Map[firstBlock - MC_FST_BLOCKS] = BE16(nextBlock)(Common::swap16(nextBlock)); |
| 721 | UpdatedBat.LastAllocated = BE16(firstBlock)(Common::swap16(firstBlock)); |
| 722 | firstBlock = nextBlock; |
| 723 | } |
| 724 | |
| 725 | UpdatedBat.FreeBlocks = BE16(BE16(UpdatedBat.FreeBlocks) - fileBlocks)(Common::swap16((Common::swap16(UpdatedBat.FreeBlocks)) - fileBlocks )); |
| 726 | UpdatedBat.UpdateCounter = BE16(BE16(UpdatedBat.UpdateCounter) + 1)(Common::swap16((Common::swap16(UpdatedBat.UpdateCounter)) + 1 )); |
| 727 | *PreviousBat = UpdatedBat; |
| 728 | if (PreviousBat == &bat ) |
| 729 | { |
| 730 | CurrentBat = &bat; |
| 731 | PreviousBat = &bat_backup; |
| 732 | } |
| 733 | else |
| 734 | { |
| 735 | CurrentBat = &bat_backup; |
| 736 | PreviousBat = &bat; |
| 737 | } |
| 738 | |
| 739 | return SUCCESS; |
| 740 | } |
| 741 | |
| 742 | u32 GCMemcard::RemoveFile(u8 index) //index in the directory array |
| 743 | { |
| 744 | if (!m_valid) |
| 745 | return NOMEMCARD; |
| 746 | if (index >= DIRLEN) |
| 747 | return DELETE_FAIL; |
| 748 | |
| 749 | u16 startingblock = BE16(dir.Dir[index].FirstBlock)(Common::swap16(dir.Dir[index].FirstBlock)); |
| 750 | u16 numberofblocks = BE16(dir.Dir[index].BlockCount)(Common::swap16(dir.Dir[index].BlockCount)); |
| 751 | |
| 752 | BlockAlloc UpdatedBat = *CurrentBat; |
| 753 | if (!UpdatedBat.ClearBlocks(startingblock, numberofblocks)) |
| 754 | return DELETE_FAIL; |
| 755 | UpdatedBat.UpdateCounter = BE16(BE16(UpdatedBat.UpdateCounter) + 1)(Common::swap16((Common::swap16(UpdatedBat.UpdateCounter)) + 1 )); |
| 756 | *PreviousBat = UpdatedBat; |
| 757 | if (PreviousBat == &bat ) |
| 758 | { |
| 759 | CurrentBat = &bat; |
| 760 | PreviousBat = &bat_backup; |
| 761 | } |
| 762 | else |
| 763 | { |
| 764 | CurrentBat = &bat_backup; |
| 765 | PreviousBat = &bat; |
| 766 | } |
| 767 | |
| 768 | Directory UpdatedDir = *CurrentDir; |
| 769 | /* |
| 770 | // TODO: determine when this is used, even on the same memory card I have seen |
| 771 | // both update to broken file, and not updated |
| 772 | *(u32*)&UpdatedDir.Dir[index].Gamecode = 0; |
| 773 | *(u16*)&UpdatedDir.Dir[index].Makercode = 0; |
| 774 | memset(UpdatedDir.Dir[index].Filename, 0, 0x20); |
| 775 | strcpy((char*)UpdatedDir.Dir[index].Filename, "Broken File000"); |
| 776 | *(u16*)&UpdatedDir.UpdateCounter = BE16(BE16(UpdatedDir.UpdateCounter) + 1); |
| 777 | |
| 778 | *PreviousDir = UpdatedDir; |
| 779 | if (PreviousDir == &dir ) |
| 780 | { |
| 781 | CurrentDir = &dir; |
| 782 | PreviousDir = &dir_backup; |
| 783 | } |
| 784 | else |
| 785 | { |
| 786 | CurrentDir = &dir_backup; |
| 787 | PreviousDir = &dir; |
| 788 | } |
| 789 | */ |
| 790 | memset(&(UpdatedDir.Dir[index]), 0xFF, DENTRY_SIZE); |
| 791 | UpdatedDir.UpdateCounter = BE16(BE16(UpdatedDir.UpdateCounter) + 1)(Common::swap16((Common::swap16(UpdatedDir.UpdateCounter)) + 1 )); |
| 792 | *PreviousDir = UpdatedDir; |
| 793 | if (PreviousDir == &dir ) |
| 794 | { |
| 795 | CurrentDir = &dir; |
| 796 | PreviousDir = &dir_backup; |
| 797 | } |
| 798 | else |
| 799 | { |
| 800 | CurrentDir = &dir_backup; |
| 801 | PreviousDir = &dir; |
| 802 | } |
| 803 | |
| 804 | return SUCCESS; |
| 805 | } |
| 806 | |
| 807 | u32 GCMemcard::CopyFrom(const GCMemcard& source, u8 index) |
| 808 | { |
| 809 | if (!m_valid || !source.m_valid) |
| 810 | return NOMEMCARD; |
| 811 | |
| 812 | DEntry tempDEntry; |
| 813 | if (!source.GetDEntry(index, tempDEntry)) |
| 814 | return NOMEMCARD; |
| 815 | |
| 816 | u32 size = source.DEntry_BlockCount(index); |
| 817 | if (size == 0xFFFF) return INVALIDFILESIZE; |
| 818 | |
| 819 | std::vector<GCMBlock> saveData; |
| 820 | saveData.reserve(size); |
| 821 | switch (source.GetSaveData(index, saveData)) |
| 822 | { |
| 823 | case FAIL: |
| 824 | return FAIL; |
| 825 | case NOMEMCARD: |
| 826 | return NOMEMCARD; |
| 827 | default: |
| 828 | return ImportFile(tempDEntry, saveData); |
| 829 | } |
| 830 | } |
| 831 | |
| 832 | u32 GCMemcard::ImportGci(const char *inputFile, const std::string &outputFile) |
| 833 | { |
| 834 | if (outputFile.empty() && !m_valid) |
| 835 | return OPENFAIL; |
| 836 | |
| 837 | File::IOFile gci(inputFile, "rb"); |
| 838 | if (!gci) |
| 839 | return OPENFAIL; |
| 840 | |
| 841 | u32 result = ImportGciInternal(gci.ReleaseHandle(), inputFile, outputFile); |
| 842 | |
| 843 | return result; |
| 844 | } |
| 845 | |
| 846 | u32 GCMemcard::ImportGciInternal(FILE* gcih, const char *inputFile, const std::string &outputFile) |
| 847 | { |
| 848 | File::IOFile gci(gcih); |
| 849 | unsigned int offset; |
| 850 | char tmp[0xD]; |
| 851 | std::string fileType; |
| 852 | SplitPath(inputFile, NULL__null, NULL__null, &fileType); |
| 853 | |
| 854 | if (!strcasecmp(fileType.c_str(), ".gci")) |
| 855 | offset = GCI; |
| 856 | else |
| 857 | { |
| 858 | gci.ReadBytes(tmp, 0xD); |
| 859 | if (!strcasecmp(fileType.c_str(), ".gcs")) |
| 860 | { |
| 861 | if (!memcmp(tmp, "GCSAVE", 6)) // Header must be uppercase |
| 862 | offset = GCS; |
| 863 | else |
| 864 | return GCSFAIL; |
| 865 | } |
| 866 | else if (!strcasecmp(fileType.c_str(), ".sav")) |
| 867 | { |
| 868 | if (!memcmp(tmp, "DATELGC_SAVE", 0xC)) // Header must be uppercase |
| 869 | offset = SAV; |
| 870 | else |
| 871 | return SAVFAIL; |
| 872 | } |
| 873 | else |
| 874 | return OPENFAIL; |
| 875 | } |
| 876 | gci.Seek(offset, SEEK_SET0); |
| 877 | |
| 878 | DEntry tempDEntry; |
| 879 | gci.ReadBytes(&tempDEntry, DENTRY_SIZE); |
| 880 | const int fStart = (int)gci.Tell(); |
| 881 | gci.Seek(0, SEEK_END2); |
| 882 | const int length = (int)gci.Tell() - fStart; |
| 883 | gci.Seek(offset + DENTRY_SIZE, SEEK_SET0); |
| 884 | |
| 885 | Gcs_SavConvert(tempDEntry, offset, length); |
| 886 | |
| 887 | if (length != BE16(tempDEntry.BlockCount)(Common::swap16(tempDEntry.BlockCount)) * BLOCK_SIZE) |
| 888 | return LENGTHFAIL; |
| 889 | if (gci.Tell() != offset + DENTRY_SIZE) // Verify correct file position |
| 890 | return OPENFAIL; |
| 891 | |
| 892 | u32 size = BE16((tempDEntry.BlockCount))(Common::swap16((tempDEntry.BlockCount))); |
| 893 | std::vector<GCMBlock> saveData; |
| 894 | saveData.reserve(size); |
| 895 | |
| 896 | for (unsigned int i = 0; i < size; ++i) |
| 897 | { |
| 898 | GCMBlock b; |
| 899 | gci.ReadBytes(b.block, BLOCK_SIZE); |
| 900 | saveData.push_back(b); |
| 901 | } |
| 902 | u32 ret; |
| 903 | if (!outputFile.empty()) |
| 904 | { |
| 905 | File::IOFile gci2(outputFile, "wb"); |
| 906 | bool completeWrite = true; |
| 907 | if (!gci2) |
| 908 | { |
| 909 | return OPENFAIL; |
| 910 | } |
| 911 | gci2.Seek(0, SEEK_SET0); |
| 912 | |
| 913 | if (!gci2.WriteBytes(&tempDEntry, DENTRY_SIZE)) |
| 914 | completeWrite = false; |
| 915 | int fileBlocks = BE16(tempDEntry.BlockCount)(Common::swap16(tempDEntry.BlockCount)); |
| 916 | gci2.Seek(DENTRY_SIZE, SEEK_SET0); |
| 917 | |
| 918 | for (int i = 0; i < fileBlocks; ++i) |
| 919 | { |
| 920 | if (!gci2.WriteBytes(saveData[i].block, BLOCK_SIZE)) |
| 921 | completeWrite = false; |
| 922 | } |
| 923 | |
| 924 | if (completeWrite) |
| 925 | ret = GCS; |
| 926 | else |
| 927 | ret = WRITEFAIL; |
| 928 | } |
| 929 | else |
| 930 | ret = ImportFile(tempDEntry, saveData); |
| 931 | |
| 932 | return ret; |
| 933 | } |
| 934 | |
| 935 | u32 GCMemcard::ExportGci(u8 index, const char *fileName, const std::string &directory) const |
| 936 | { |
| 937 | File::IOFile gci; |
| 938 | int offset = GCI; |
| 939 | if (!fileName) |
| 940 | { |
| 941 | std::string gciFilename; |
| 942 | if (!GCI_FileName(index, gciFilename)) return SUCCESS; |
| 943 | gci.Open(directory + DIR_SEP"/" + gciFilename, "wb"); |
| 944 | } |
| 945 | else |
| 946 | { |
| 947 | gci.Open(fileName, "wb"); |
| 948 | |
| 949 | std::string fileType; |
| 950 | SplitPath(fileName, NULL__null, NULL__null, &fileType); |
| 951 | if (!strcasecmp(fileType.c_str(), ".gcs")) |
| 952 | { |
| 953 | offset = GCS; |
| 954 | } |
| 955 | else if (!strcasecmp(fileType.c_str(), ".sav")) |
| 956 | { |
| 957 | offset = SAV; |
| 958 | } |
| 959 | } |
| 960 | |
| 961 | if (!gci) |
| 962 | return OPENFAIL; |
| 963 | |
| 964 | gci.Seek(0, SEEK_SET0); |
| 965 | |
| 966 | switch(offset) |
| 967 | { |
| 968 | case GCS: |
| 969 | u8 gcsHDR[GCS]; |
| 970 | memset(gcsHDR, 0, GCS); |
| 971 | memcpy(gcsHDR, "GCSAVE", 6); |
| 972 | gci.WriteArray(gcsHDR, GCS); |
| 973 | break; |
| 974 | |
| 975 | case SAV: |
| 976 | u8 savHDR[SAV]; |
| 977 | memset(savHDR, 0, SAV); |
| 978 | memcpy(savHDR, "DATELGC_SAVE", 0xC); |
| 979 | gci.WriteArray(savHDR, SAV); |
| 980 | break; |
| 981 | } |
| 982 | |
| 983 | DEntry tempDEntry; |
| 984 | if (!GetDEntry(index, tempDEntry)) |
| 985 | { |
| 986 | return NOMEMCARD; |
| 987 | } |
| 988 | |
| 989 | Gcs_SavConvert(tempDEntry, offset); |
| 990 | gci.WriteBytes(&tempDEntry, DENTRY_SIZE); |
| 991 | |
| 992 | u32 size = DEntry_BlockCount(index); |
| 993 | if (size == 0xFFFF) |
| 994 | { |
| 995 | return FAIL; |
| 996 | } |
| 997 | |
| 998 | std::vector<GCMBlock> saveData; |
| 999 | saveData.reserve(size); |
| 1000 | |
| 1001 | switch(GetSaveData(index, saveData)) |
| 1002 | { |
| 1003 | case FAIL: |
| 1004 | return FAIL; |
| 1005 | case NOMEMCARD: |
| 1006 | return NOMEMCARD; |
| 1007 | } |
| 1008 | gci.Seek(DENTRY_SIZE + offset, SEEK_SET0); |
| 1009 | for (unsigned int i = 0; i < size; ++i) |
| 1010 | { |
| 1011 | gci.WriteBytes(saveData[i].block, BLOCK_SIZE); |
| 1012 | } |
| 1013 | |
| 1014 | if (gci.IsGood()) |
| 1015 | return SUCCESS; |
| 1016 | else |
| 1017 | return WRITEFAIL; |
| 1018 | } |
| 1019 | |
| 1020 | void GCMemcard::Gcs_SavConvert(DEntry &tempDEntry, int saveType, int length) |
| 1021 | { |
| 1022 | switch(saveType) |
| 1023 | { |
| 1024 | case GCS: |
| 1025 | { // field containing the Block count as displayed within |
| 1026 | // the GameSaves software is not stored in the GCS file. |
| 1027 | // It is stored only within the corresponding GSV file. |
| 1028 | // If the GCS file is added without using the GameSaves software, |
| 1029 | // the value stored is always "1" |
| 1030 | *(u16*)&tempDEntry.BlockCount = BE16(length / BLOCK_SIZE)(Common::swap16(length / BLOCK_SIZE)); |
| 1031 | } |
| 1032 | break; |
| 1033 | case SAV: |
| 1034 | // swap byte pairs |
| 1035 | // 0x2C and 0x2D, 0x2E and 0x2F, 0x30 and 0x31, 0x32 and 0x33, |
| 1036 | // 0x34 and 0x35, 0x36 and 0x37, 0x38 and 0x39, 0x3A and 0x3B, |
| 1037 | // 0x3C and 0x3D,0x3E and 0x3F. |
| 1038 | // It seems that sav files also swap the BIFlags... |
| 1039 | ByteSwap(&tempDEntry.Unused1, &tempDEntry.BIFlags); |
| 1040 | ArrayByteSwap((tempDEntry.ImageOffset))(ByteSwap((tempDEntry.ImageOffset), (tempDEntry.ImageOffset)+ sizeof(u8)));; |
| 1041 | ArrayByteSwap(&(tempDEntry.ImageOffset[2]))(ByteSwap(&(tempDEntry.ImageOffset[2]), &(tempDEntry. ImageOffset[2])+sizeof(u8)));; |
| 1042 | ArrayByteSwap((tempDEntry.IconFmt))(ByteSwap((tempDEntry.IconFmt), (tempDEntry.IconFmt)+sizeof(u8 )));; |
| 1043 | ArrayByteSwap((tempDEntry.AnimSpeed))(ByteSwap((tempDEntry.AnimSpeed), (tempDEntry.AnimSpeed)+sizeof (u8)));; |
| 1044 | ByteSwap(&tempDEntry.Permissions, &tempDEntry.CopyCounter); |
| 1045 | ArrayByteSwap((tempDEntry.FirstBlock))(ByteSwap((tempDEntry.FirstBlock), (tempDEntry.FirstBlock)+sizeof (u8)));; |
| 1046 | ArrayByteSwap((tempDEntry.BlockCount))(ByteSwap((tempDEntry.BlockCount), (tempDEntry.BlockCount)+sizeof (u8)));; |
| 1047 | ArrayByteSwap((tempDEntry.Unused2))(ByteSwap((tempDEntry.Unused2), (tempDEntry.Unused2)+sizeof(u8 )));; |
| 1048 | ArrayByteSwap((tempDEntry.CommentsAddr))(ByteSwap((tempDEntry.CommentsAddr), (tempDEntry.CommentsAddr )+sizeof(u8)));; |
| 1049 | ArrayByteSwap(&(tempDEntry.CommentsAddr[2]))(ByteSwap(&(tempDEntry.CommentsAddr[2]), &(tempDEntry .CommentsAddr[2])+sizeof(u8)));; |
| 1050 | break; |
| 1051 | default: |
| 1052 | break; |
| 1053 | } |
| 1054 | } |
| 1055 | |
| 1056 | bool GCMemcard::ReadBannerRGBA8(u8 index, u32* buffer) const |
| 1057 | { |
| 1058 | if (!m_valid) |
| 1059 | return false; |
| 1060 | |
| 1061 | int flags = CurrentDir->Dir[index].BIFlags; |
| 1062 | // Timesplitters 2 is the only game that I see this in |
| 1063 | // May be a hack |
| 1064 | if (flags == 0xFB) flags = ~flags; |
| 1065 | |
| 1066 | int bnrFormat = (flags&3); |
| 1067 | |
| 1068 | if (bnrFormat == 0) |
| 1069 | return false; |
| 1070 | |
| 1071 | u32 DataOffset = BE32(CurrentDir->Dir[index].ImageOffset)(Common::swap32(CurrentDir->Dir[index].ImageOffset)); |
| 1072 | u32 DataBlock = BE16(CurrentDir->Dir[index].FirstBlock)(Common::swap16(CurrentDir->Dir[index].FirstBlock)) - MC_FST_BLOCKS; |
| 1073 | |
| 1074 | if ((DataBlock > maxBlock) || (DataOffset == 0xFFFFFFFF)) |
| 1075 | { |
| 1076 | return false; |
| 1077 | } |
| 1078 | |
| 1079 | const int pixels = 96*32; |
| 1080 | |
| 1081 | if (bnrFormat&1) |
| 1082 | { |
| 1083 | u8 *pxdata = (u8* )(mc_data_blocks[DataBlock].block + DataOffset); |
| 1084 | u16 *paldata = (u16*)(mc_data_blocks[DataBlock].block + DataOffset + pixels); |
| 1085 | |
| 1086 | decodeCI8image(buffer, pxdata, paldata, 96, 32); |
| 1087 | } |
| 1088 | else |
| 1089 | { |
| 1090 | u16 *pxdata = (u16*)(mc_data_blocks[DataBlock].block + DataOffset); |
| 1091 | |
| 1092 | decode5A3image(buffer, pxdata, 96, 32); |
| 1093 | } |
| 1094 | return true; |
| 1095 | } |
| 1096 | |
| 1097 | u32 GCMemcard::ReadAnimRGBA8(u8 index, u32* buffer, u8 *delays) const |
| 1098 | { |
| 1099 | if (!m_valid) |
| 1100 | return 0; |
| 1101 | |
| 1102 | // To ensure only one type of icon is used |
| 1103 | // Sonic Heroes it the only game I have seen that tries to use a CI8 and RGB5A3 icon |
| 1104 | //int fmtCheck = 0; |
| 1105 | |
| 1106 | int formats = BE16(CurrentDir->Dir[index].IconFmt)(Common::swap16(CurrentDir->Dir[index].IconFmt)); |
| 1107 | int fdelays = BE16(CurrentDir->Dir[index].AnimSpeed)(Common::swap16(CurrentDir->Dir[index].AnimSpeed)); |
| 1108 | |
| 1109 | int flags = CurrentDir->Dir[index].BIFlags; |
| 1110 | // Timesplitters 2 and 3 is the only game that I see this in |
| 1111 | // May be a hack |
| 1112 | //if (flags == 0xFB) flags = ~flags; |
| 1113 | // Batten Kaitos has 0x65 as flag too. Everything but the first 3 bytes seems irrelevant. |
| 1114 | // Something similar happens with Wario Ware Inc. AnimSpeed |
| 1115 | |
| 1116 | int bnrFormat = (flags&3); |
| 1117 | |
| 1118 | u32 DataOffset = BE32(CurrentDir->Dir[index].ImageOffset)(Common::swap32(CurrentDir->Dir[index].ImageOffset)); |
| 1119 | u32 DataBlock = BE16(CurrentDir->Dir[index].FirstBlock)(Common::swap16(CurrentDir->Dir[index].FirstBlock)) - MC_FST_BLOCKS; |
| 1120 | |
| 1121 | if ((DataBlock > maxBlock) || (DataOffset == 0xFFFFFFFF)) |
| 1122 | { |
| 1123 | return 0; |
| 1124 | } |
| 1125 | |
| 1126 | u8* animData = (u8*)(mc_data_blocks[DataBlock].block + DataOffset); |
| 1127 | |
| 1128 | switch (bnrFormat) |
| 1129 | { |
| 1130 | case 1: |
| 1131 | animData += 96*32 + 2*256; // image+palette |
| 1132 | break; |
| 1133 | case 2: |
| 1134 | animData += 96*32*2; |
| 1135 | break; |
| 1136 | } |
| 1137 | |
| 1138 | int fmts[8]; |
| 1139 | u8* data[8]; |
| 1140 | int frames = 0; |
| 1141 | |
| 1142 | for (int i = 0; i < 8; i++) |
| 1143 | { |
| 1144 | fmts[i] = (formats >> (2*i))&3; |
| 1145 | delays[i] = ((fdelays >> (2*i))&3); |
| 1146 | data[i] = animData; |
| 1147 | |
| 1148 | if (!delays[i]) |
| 1149 | { |
| 1150 | //First icon_speed = 0 indicates there aren't any more icons |
| 1151 | break; |
| 1152 | } |
| 1153 | //If speed is set there is an icon (it can be a "blank frame") |
| 1154 | frames++; |
| 1155 | if (fmts[i] != 0) |
| 1156 | { |
| 1157 | switch (fmts[i]) |
| 1158 | { |
| 1159 | case CI8SHARED: // CI8 with shared palette |
| 1160 | animData += 32*32; |
| 1161 | break; |
| 1162 | case RGB5A3: // RGB5A3 |
| 1163 | animData += 32*32*2; |
| 1164 | break; |
| 1165 | case CI8: // CI8 with own palette |
| 1166 | animData += 32*32 + 2*256; |
| 1167 | break; |
| 1168 | } |
| 1169 | } |
| 1170 | } |
| 1171 | |
| 1172 | u16* sharedPal = (u16*)(animData); |
| 1173 | int j = 0; |
| 1174 | |
| 1175 | for (int i = 0; i < 8; i++) |
| 1176 | { |
| 1177 | |
| 1178 | if (!delays[i]) |
| 1179 | { |
| 1180 | //First icon_speed = 0 indicates there aren't any more icons |
| 1181 | break; |
| 1182 | } |
| 1183 | if (fmts[i] != 0) |
| 1184 | { |
| 1185 | switch (fmts[i]) |
| 1186 | { |
| 1187 | case CI8SHARED: // CI8 with shared palette |
| 1188 | decodeCI8image(buffer,data[i],sharedPal,32,32); |
| 1189 | buffer += 32*32; |
| 1190 | break; |
| 1191 | case RGB5A3: // RGB5A3 |
| 1192 | decode5A3image(buffer, (u16*)(data[i]), 32, 32); |
| 1193 | buffer += 32*32; |
| 1194 | break; |
| 1195 | case CI8: // CI8 with own palette |
| 1196 | u16 *paldata = (u16*)(data[i] + 32*32); |
| 1197 | decodeCI8image(buffer, data[i], paldata, 32, 32); |
| 1198 | buffer += 32*32; |
| 1199 | break; |
| 1200 | } |
| 1201 | } |
| 1202 | else |
| 1203 | { |
| 1204 | //Speed is set but there's no actual icon |
| 1205 | //This is used to reduce animation speed in Pikmin and Luigi's Mansion for example |
| 1206 | //These "blank frames" show the next icon |
| 1207 | for(j=i; j<8;++j) |
| 1208 | { |
| 1209 | if (fmts[j] != 0) |
| 1210 | { |
| 1211 | switch (fmts[j]) |
| 1212 | { |
| 1213 | case CI8SHARED: // CI8 with shared palette |
| 1214 | decodeCI8image(buffer,data[j],sharedPal,32,32); |
| 1215 | break; |
| 1216 | case RGB5A3: // RGB5A3 |
| 1217 | decode5A3image(buffer, (u16*)(data[j]), 32, 32); |
| 1218 | buffer += 32*32; |
| 1219 | break; |
| 1220 | case CI8: // CI8 with own palette |
| 1221 | u16 *paldata = (u16*)(data[j] + 32*32); |
| 1222 | decodeCI8image(buffer, data[j], paldata, 32, 32); |
| 1223 | buffer += 32*32; |
| 1224 | break; |
| 1225 | } |
| 1226 | } |
| 1227 | } |
| 1228 | } |
| 1229 | } |
| 1230 | |
| 1231 | return frames; |
| 1232 | } |
| 1233 | |
| 1234 | |
| 1235 | bool GCMemcard::Format(u8 * card_data, bool sjis, u16 SizeMb) |
| 1236 | { |
| 1237 | if (!card_data) |
| 1238 | return false; |
| 1239 | memset(card_data, 0xFF, BLOCK_SIZE*3); |
| 1240 | memset(card_data + BLOCK_SIZE*3, 0, BLOCK_SIZE*2); |
| 1241 | |
| 1242 | GCMC_Header gcp; |
| 1243 | gcp.hdr = (Header*)card_data; |
| 1244 | gcp.dir = (Directory *)(card_data + BLOCK_SIZE); |
| 1245 | gcp.dir_backup = (Directory *)(card_data + BLOCK_SIZE*2); |
| 1246 | gcp.bat = (BlockAlloc *)(card_data + BLOCK_SIZE*3); |
| 1247 | gcp.bat_backup = (BlockAlloc *)(card_data + BLOCK_SIZE*4); |
| 1248 | |
| 1249 | *(u16*)gcp.hdr->SizeMb = BE16(SizeMb)(Common::swap16(SizeMb)); |
| 1250 | gcp.hdr->Encoding = BE16(sjis ? 1 : 0)(Common::swap16(sjis ? 1 : 0)); |
| 1251 | |
| 1252 | FormatInternal(gcp); |
| 1253 | return true; |
| 1254 | } |
| 1255 | |
| 1256 | bool GCMemcard::Format(bool sjis, u16 SizeMb) |
| 1257 | { |
| 1258 | memset(&hdr, 0xFF, BLOCK_SIZE); |
| 1259 | memset(&dir, 0xFF, BLOCK_SIZE); |
| 1260 | memset(&dir_backup, 0xFF, BLOCK_SIZE); |
| 1261 | memset(&bat, 0, BLOCK_SIZE); |
| 1262 | memset(&bat_backup, 0, BLOCK_SIZE); |
| 1263 | |
| 1264 | GCMC_Header gcp; |
| 1265 | gcp.hdr = &hdr; |
| 1266 | gcp.dir = &dir; |
| 1267 | gcp.dir_backup = &dir_backup; |
| 1268 | gcp.bat = &bat; |
| 1269 | gcp.bat_backup = &bat_backup; |
| 1270 | |
| 1271 | *(u16*)hdr.SizeMb = BE16(SizeMb)(Common::swap16(SizeMb)); |
| 1272 | hdr.Encoding = BE16(sjis ? 1 : 0)(Common::swap16(sjis ? 1 : 0)); |
| 1273 | FormatInternal(gcp); |
| 1274 | |
| 1275 | m_sizeMb = SizeMb; |
| 1276 | maxBlock = (u32)m_sizeMb * MBIT_TO_BLOCKS; |
| 1277 | mc_data_blocks.reserve(maxBlock - MC_FST_BLOCKS); |
| 1278 | for (u32 i = 0; i < (maxBlock - MC_FST_BLOCKS); ++i) |
| 1279 | { |
| 1280 | GCMBlock b; |
| 1281 | mc_data_blocks.push_back(b); |
| 1282 | } |
| 1283 | |
| 1284 | initDirBatPointers(); |
| 1285 | m_valid = true; |
| 1286 | |
| 1287 | return Save(); |
| 1288 | } |
| 1289 | |
| 1290 | void GCMemcard::FormatInternal(GCMC_Header &GCP) |
| 1291 | { |
| 1292 | Header *p_hdr = GCP.hdr; |
| 1293 | u64 rand = CEXIIPL::GetGCTime(); |
| 1294 | p_hdr->formatTime = Common::swap64(rand); |
| 1295 | |
| 1296 | for(int i = 0; i < 12; i++) |
| 1297 | { |
| 1298 | rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16); |
| 1299 | p_hdr->serial[i] = (u8)(g_SRAM.flash_id[0][i] + (u32)rand); |
| 1300 | rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16); |
| 1301 | rand &= (u64)0x0000000000007fffULL; |
| 1302 | } |
| 1303 | p_hdr->SramBias = g_SRAM.counter_bias; |
| 1304 | p_hdr->SramLang = g_SRAM.lang; |
| 1305 | // TODO: determine the purpose of Unk2 1 works for slot A, 0 works for both slot A and slot B |
| 1306 | *(u32*)&p_hdr->Unk2 = 0; // = _viReg[55]; static vu16* const _viReg = (u16*)0xCC002000; |
| 1307 | *(u16*)&p_hdr->deviceID = 0; |
| 1308 | calc_checksumsBE((u16*)p_hdr, 0xFE, &p_hdr->Checksum, &p_hdr->Checksum_Inv); |
| 1309 | |
| 1310 | Directory *p_dir = GCP.dir, |
| 1311 | *p_dir_backup = GCP.dir_backup; |
| 1312 | *(u16*)&p_dir->UpdateCounter = 0; |
| 1313 | p_dir_backup->UpdateCounter = BE16(1)(Common::swap16(1)); |
| 1314 | calc_checksumsBE((u16*)p_dir, 0xFFE, &p_dir->Checksum, &p_dir->Checksum_Inv); |
| 1315 | calc_checksumsBE((u16*)p_dir_backup, 0xFFE, &p_dir_backup->Checksum, &p_dir_backup->Checksum_Inv); |
| 1316 | |
| 1317 | BlockAlloc *p_bat = GCP.bat, |
| 1318 | *p_bat_backup = GCP.bat_backup; |
| 1319 | p_bat_backup->UpdateCounter = BE16(1)(Common::swap16(1)); |
| 1320 | p_bat->FreeBlocks = *(u16*)&p_bat_backup->FreeBlocks = BE16(( BE16(p_hdr->SizeMb) * MBIT_TO_BLOCKS) - MC_FST_BLOCKS)(Common::swap16(( (Common::swap16(p_hdr->SizeMb)) * MBIT_TO_BLOCKS ) - MC_FST_BLOCKS)); |
| 1321 | p_bat->LastAllocated = p_bat_backup->LastAllocated = BE16(4)(Common::swap16(4)); |
| 1322 | calc_checksumsBE((u16*)p_bat+2, 0xFFE, &p_bat->Checksum, &p_bat->Checksum_Inv); |
| 1323 | calc_checksumsBE((u16*)p_bat_backup+2, 0xFFE, &p_bat_backup->Checksum, &p_bat_backup->Checksum_Inv); |
| 1324 | } |
| 1325 | |
| 1326 | void GCMemcard::CARD_GetSerialNo(u32 *serial1,u32 *serial2) |
| 1327 | { |
| 1328 | u32 serial[8]; |
| 1329 | |
| 1330 | for (int i = 0; i < 8; i++) |
| 1331 | { |
| 1332 | memcpy(&serial[i], (u8 *) &hdr+(i*4), 4); |
| 1333 | } |
| 1334 | |
| 1335 | *serial1 = serial[0]^serial[2]^serial[4]^serial[6]; |
| 1336 | *serial2 = serial[1]^serial[3]^serial[5]^serial[7]; |
| 1337 | } |
| 1338 | |
| 1339 | |
| 1340 | /*************************************************************/ |
| 1341 | /* FZEROGX_MakeSaveGameValid */ |
| 1342 | /* (use just before writing a F-Zero GX system .gci file) */ |
| 1343 | /* */ |
| 1344 | /* Parameters: */ |
| 1345 | /* direntry: [Description needed] */ |
| 1346 | /* FileBuffer: [Description needed] */ |
| 1347 | /* */ |
| 1348 | /* Returns: Error code */ |
| 1349 | /*************************************************************/ |
| 1350 | |
| 1351 | s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer) |
| 1352 | { |
| 1353 | u32 i,j; |
| 1354 | u32 serial1,serial2; |
| 1355 | u16 chksum = 0xFFFF; |
| 1356 | int block = 0; |
| 1357 | |
| 1358 | // check for F-Zero GX system file |
| 1359 | if (strcmp((char*)direntry.Filename,"f_zero.dat")!=0) return 0; |
| 1360 | |
| 1361 | // get encrypted destination memory card serial numbers |
| 1362 | CARD_GetSerialNo(&serial1,&serial2); |
| 1363 | |
| 1364 | // set new serial numbers |
| 1365 | *(u16*)&FileBuffer[1].block[0x0066] = BE16(BE32(serial1) >> 16)(Common::swap16((Common::swap32(serial1)) >> 16)); |
| 1366 | *(u16*)&FileBuffer[3].block[0x1580] = BE16(BE32(serial2) >> 16)(Common::swap16((Common::swap32(serial2)) >> 16)); |
| 1367 | *(u16*)&FileBuffer[1].block[0x0060] = BE16(BE32(serial1) & 0xFFFF)(Common::swap16((Common::swap32(serial1)) & 0xFFFF)); |
| 1368 | *(u16*)&FileBuffer[1].block[0x0200] = BE16(BE32(serial2) & 0xFFFF)(Common::swap16((Common::swap32(serial2)) & 0xFFFF)); |
| 1369 | |
| 1370 | // calc 16-bit checksum |
| 1371 | for (i=0x02;i<0x8000;i++) |
| 1372 | { |
| 1373 | chksum ^= (FileBuffer[block].block[i-(block*0x2000)]&0xFF); |
| 1374 | for (j=8; j > 0; j--) |
| 1375 | { |
| 1376 | if (chksum&1) chksum = (chksum>>1)^0x8408; |
| 1377 | else chksum >>= 1; |
| 1378 | } |
| 1379 | if (!(i%0x2000)) block ++; |
| 1380 | } |
| 1381 | |
| 1382 | // set new checksum |
| 1383 | *(u16*)&FileBuffer[0].block[0x00] = BE16(~chksum)(Common::swap16(~chksum)); |
| 1384 | |
| 1385 | return 1; |
| 1386 | } |
| 1387 | |
| 1388 | /***********************************************************/ |
| 1389 | /* PSO_MakeSaveGameValid */ |
| 1390 | /* (use just before writing a PSO system .gci file) */ |
| 1391 | /* */ |
| 1392 | /* Parameters: */ |
| 1393 | /* direntry: [Description needed] */ |
| 1394 | /* FileBuffer: [Description needed] */ |
| 1395 | /* */ |
| 1396 | /* Returns: Error code */ |
| 1397 | /***********************************************************/ |
| 1398 | |
| 1399 | s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer) |
| 1400 | { |
| 1401 | u32 i,j; |
| 1402 | u32 chksum; |
| 1403 | u32 crc32LUT[256]; |
| 1404 | u32 serial1,serial2; |
| 1405 | u32 pso3offset = 0x00; |
| 1406 | |
| 1407 | // check for PSO1&2 system file |
| 1408 | if (strcmp((char*)direntry.Filename,"PSO_SYSTEM")!=0) |
| 1409 | { |
| 1410 | // check for PSO3 system file |
| 1411 | if (strcmp((char*)direntry.Filename,"PSO3_SYSTEM")==0) |
| 1412 | { |
| 1413 | // PSO3 data block size adjustment |
| 1414 | pso3offset = 0x10; |
| 1415 | } |
| 1416 | else |
| 1417 | { |
| 1418 | // nothing to do |
| 1419 | return 0; |
| 1420 | } |
| 1421 | } |
| 1422 | |
| 1423 | // get encrypted destination memory card serial numbers |
| 1424 | CARD_GetSerialNo(&serial1,&serial2); |
| 1425 | |
| 1426 | // set new serial numbers |
| 1427 | *(u32*)&FileBuffer[1].block[0x0158] = serial1; |
| 1428 | *(u32*)&FileBuffer[1].block[0x015C] = serial2; |
| 1429 | |
| 1430 | // generate crc32 LUT |
| 1431 | for (i=0; i < 256; i++) |
| 1432 | { |
| 1433 | chksum = i; |
| 1434 | for (j=8; j > 0; j--) |
| 1435 | { |
| 1436 | if (chksum & 1) |
| 1437 | chksum = (chksum>>1)^0xEDB88320; |
| 1438 | else |
| 1439 | chksum >>= 1; |
| 1440 | } |
| 1441 | |
| 1442 | crc32LUT[i] = chksum; |
| 1443 | } |
| 1444 | |
| 1445 | // PSO initial crc32 value |
| 1446 | chksum = 0xDEBB20E3; |
| 1447 | |
| 1448 | // calc 32-bit checksum |
| 1449 | for (i=0x004C; i < 0x0164+pso3offset; i++) |
| 1450 | { |
| 1451 | chksum = ((chksum>>8)&0xFFFFFF)^crc32LUT[(chksum^FileBuffer[1].block[i])&0xFF]; |
| 1452 | } |
| 1453 | |
| 1454 | // set new checksum |
| 1455 | *(u32*)&FileBuffer[1].block[0x0048] = BE32(chksum^0xFFFFFFFF)(Common::swap32(chksum^0xFFFFFFFF)); |
| 1456 | |
| 1457 | return 1; |
| 1458 | } |