Advertisement
PabloMK7

Bad Apple but it's a Mario Kart character

Jun 28th, 2024
951
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.11 KB | None | 0 0
  1. Bad Apple on Mario Kart 7 was achieved with a small modification to the CTGP-7 modpack.
  2. You can see the video here: https://www.youtube.com/watch?v=OwY082cDNK8
  3.  
  4. 1) A custom character pack (.chpack) for CTGP-7 was created containing a custom driver, kart and emblem models. The custom driver consists of a 4:3 double-sided plane with a 128x128 texture mapped to it. This texture uses the L8 encoding (8 bits of luminance per pixel, greyscale), resulting in a texture of 0x4000 bytes (about 16KB). This texture is mostly black, but a small byte pattern was added so it could be found in RAM later.
  5.  
  6. 2) The Bad Apple video was converted to frames using cv2 in python, resulting in 13166 frames. Each frame was then converted to .bclim using an script with the following tool: https://github.com/FanTranslatorsInternational/Kuriimu2. After all the bclims were generated, the raw pixel data was combined into a huge 212MB (13166 * 0x4000 bytes) apple.bin file placed on the root of the Nintendo 3DS SD card.
  7.  
  8. 3) A modification of the CTGP-7 source code was made, the following piece of code runs once per frame, when the game tries to draw the timer on screen.
  9. ```
  10. static u32 ba_frame = 0;
  11. static u32 ba_total = 13167;
  12. static File* apple = nullptr;
  13. static void* apppleTempBuf = nullptr;
  14.  
  15. // Called once per frame, at 60fps
  16. void OnRaceFrame() {
  17. constexpr u32 badAppleSize = 128 * 128;
  18. if (!badApplePtr) {
  19. // Byte pattern added to the texture, as explained in step 1.
  20. const u8 tex_pat[] = {0x12, 0x34, 0x3E, 0x3E, 0x57, 0x68, 0x3E, 0x3E};
  21.  
  22. // Scan all VRAM to find the texture. It is small enough to be found in one frame.
  23. badApplePtr =(u8*)memmem((u8*)0x1F000000, 0x00600000, tex_pat, sizeof(tex_pat));
  24. } else {
  25. // Open the file and create a temp buffer if they don't exist
  26. if (!apple) apple = new File("/apple.bin", File::READ);
  27. if (!apppleTempBuf) apppleTempBuf = memalign(0x1000, 0x4000);
  28.  
  29. // Seek the file to the right location based on the current frame
  30. apple->Seek(128 * 128 * ba_frame++, File::SeekPos::SET);
  31.  
  32. // Read the frame and copy it to VRAM (not possible to read to VRAM directly)
  33. // This replaces the texture of the plane, making the frame display
  34. apple->Read(apppleTempBuf, 128*128);
  35. memcpy(badApplePtr, apppleTempBuf, 128 * 128);
  36.  
  37. // If it's the last frame, loop
  38. if (ba_frame == ba_total) ba_frame = 0;
  39. }
  40. }
  41. ```
  42.  
  43. NOTE: The video was recorded on the Citra 3DS emulator, but should work on a real 3DS. However, I doubt the real console SD card will be fast enough to read the data. A compression/decompression algorithm may need to be implemented, or the texture size reduced. However that is outside the scope of the video :P
  44.  
  45. NOTE2: Since the OnRaceFrame function only runs when the timer is counting, the "playback" is stopped after the race ends or before it starts. This was helpful as I could let the game play the replay again and then stitch the video together, without having to adjust the frame counter. You can see this at the ending of the video, as the playback stops just after crossing the finish line (I didn't bother doing a second stitch for only 3 seconds of bad apple remaining).
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement