Replacing (Disk) Files

Discussion in 'Wii U - Hacking & Backup Loaders' started by BullyWiiPlaza, Mar 30, 2017.

  1. BullyWiiPlaza
    OP

    BullyWiiPlaza Nintendo Hacking <3

    Member
    1,773
    1,441
    Aug 2, 2014
    Germany
    For the TCP Gecko Installer I tried to implement a function to replace files on games. However, I'm getting an ACCESS_ERROR when calling FSOpenFile():
    Code:
    case COMMAND_REPLACE_FILE: {
        // Receive the file path
        char file_path[FS_MAX_FULLPATH_SIZE] = {0};
        receiveString(bss, clientfd, file_path, FS_MAX_FULLPATH_SIZE);
    
        considerInitializingFileSystem();
    
        // Create an empty file for writing. Its contents will be erased
        int handle;
        int status = FSOpenFile(client, commandBlock, file_path, "w", &handle, FS_RET_ALL_ERROR);
    
        if (status == FS_STATUS_OK) {
            // Send the OK status
            ((int *) buffer)[0] = status;
            ret = sendwait(bss, clientfd, buffer, 4);
            ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (OK status)")
    
            // Set the file handle position to the beginning
            ret = FSSetPosFile(client, commandBlock, handle, 0, FS_RET_ALL_ERROR);
            ASSERT_FUNCTION_SUCCEEDED(ret, "FSSetPosFile")
    
            // Allocate the file bytes buffer
            unsigned int file_buffer_size = 0x2000;
            char *fileBuffer = (char *) OSAllocFromSystem(file_buffer_size, FS_IO_BUFFER_ALIGN);
            ASSERT_ALLOCATED(fileBuffer, "File buffer")
    
            // Send the maximum file buffer size
            ret = sendwait(bss, clientfd, &file_buffer_size, 4);
            ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (maximum file buffer size)")
    
            while (true) {
                // Receive the data bytes length
                unsigned int dataLength;
                ret = recvwait(bss, clientfd, &dataLength, 4);
                ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (File bytes length)")
                ASSERT_MAXIMUM_HOLDS(file_buffer_size, dataLength, "File buffer overrun attempted")
    
                if (dataLength > 0) {
                    // Receive the data
                    ret = recvwait(bss, clientfd, fileBuffer, dataLength);
                    ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (File buffer)")
    
                    // Write the data (and advance file handle position implicitly)
                    ret = FSWriteFile(client, commandBlock, fileBuffer, 1,
                                      dataLength, handle, 0, FS_RET_ALL_ERROR);
                    ASSERT_FUNCTION_SUCCEEDED(ret, "FSWriteFile")
                } else {
                    // Done with receiving the new file
                    break;
                }
            }
    
            /*// Flush the file back
            ret = FSFlushFile(client, commandBlock, handle, FS_RET_ALL_ERROR);
            CHECK_FUNCTION_FAILED(ret, "FSFlushFile")*/
    
            // Close the file
            ret = FSCloseFile(client, commandBlock, handle, FS_RET_ALL_ERROR);
            ASSERT_FUNCTION_SUCCEEDED(ret, "FSCloseFile")
    
            // Free the file buffer
            OSFreeToSystem(fileBuffer);
        } else {
            // Send the status
            ((int *) buffer)[0] = status;
            ret = sendwait(bss, clientfd, buffer, 4);
            ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (status)")
        }
    
        break;
    }
    Client code in Java:
    Code:
    public FileSystemStatus replaceFile(String remoteFilePath, String localFilePath) throws IOException
    {
        // Read the file bytes
        Path localFile = Paths.get(localFilePath);
        byte[] fileBytes = Files.readAllBytes(localFile);
    
        try (CloseableReentrantLock ignored = reentrantLock.acquire())
        {
            // Send the command and remote file path
            sendCommand(Command.REPLACE_REMOTE_FILE);
            writeString(remoteFilePath);
            dataSender.flush();
    
            FileSystemStatus status = readFileSystemStatus();
    
            // Continue if the file has been opened successfully
            if (status == FileSystemStatus.OK)
            {
                sendFileBytes(fileBytes);
            }
    
            return status;
        }
    }
    
    private void sendFileBytes(byte[] fileBytes) throws IOException
    {
        int maximumChunkSize = dataReceiver.readInt();
        int fileBytesIndex = 0;
    
        // Send the file bytes in chunks
        while (fileBytesIndex < fileBytes.length)
        {
            int chunkSize = fileBytes.length - fileBytesIndex;
    
            if (chunkSize > maximumChunkSize)
            {
                chunkSize = maximumChunkSize;
            }
    
            // Send the current chunk size
            dataSender.writeInt(chunkSize);
            sendPartialBytes(fileBytes, fileBytesIndex, chunkSize);
            dataSender.flush();
    
            fileBytesIndex += chunkSize;
        }
    
        // Let the server know that we're done
        dataSender.writeInt(0);
        dataSender.flush();
    }
    
    private void sendPartialBytes(byte[] bytes, int startingIndex, int length) throws IOException
    {
        byte[] sendBytes = new byte[length];
        System.arraycopy(bytes, startingIndex, sendBytes, 0, length);
        dataSender.write(sendBytes);
    }
    In case you're wondering, Cafiine does not actually replace files the same way I'm trying to. It only fools the Wii U into pulling the files from the network instead of the disk. However, I'm trying to just replace the file so the replacement file is being used from now on instead without requesting it over the network over and over again. I'm not sure if this is even possible since the SDK says this about the error message:
    Code:
    Access mode is invalid (e.g. specified "r+" for read-only media).
    It might mean that disks just aren't writable (I'm using the digital version though) but isn't there maybe a way to do it? This project might help as well. Thanks :)

    @dimok
    @QuarkTheAwesome
    @Maschell
    @FIX94
    @wj44
    @NWPlayer123
     
    Last edited by BullyWiiPlaza, Mar 30, 2017
  2. Don Jon

    Don Jon GBAtemp Fan

    Member
    481
    188
    Nov 20, 2015
    United States
    Good luck!
     
  3. wj44

    wj44 GBAtemp Fan

    Member
    477
    354
    Jun 18, 2015
    Gambia, The
    If you really want to replace the file you either need to use libiosuhax or the MCPCopyFile Command(eShop games only). Alternativly You can just write the file to the sd and open It everywhere with libiosuhax.
     
    BullyWiiPlaza likes this.
  4. BullyWiiPlaza
    OP

    BullyWiiPlaza Nintendo Hacking <3

    Member
    1,773
    1,441
    Aug 2, 2014
    Germany
    Wouldn't that be permanent? A temporary replacement would be better I guess since FTPiiu everywhere is probably best suited for permanent replacements. How would go about using libiosuhax to replace the file(s)?
    The code should look similar to this then?
    Code:
    // initialize IOSUHAX
    int res = IOSUHAX_Open();
    
    // initialize FSA fd
    int fsaFd = IOSUHAX_FSA_Open();
    
    // mount "/dev/odd03" to "/vol/storage_virt_odd" and bind it to our virt PPC path "odd:/"
    // if 3rd parameter is NULL it only binds the /vol path (e.g. /vol/system)  to our virt PPC path
    res = mount_fs("odd", fsaFd, "/dev/odd03", "/vol/storage_virt_odd");
    
    // Open the file, write and close it again
    IOSUHAX_FSA_OpenFile(fsaFd, systemXmlPath, "wb", &slcFd);
    IOSUHAX_FSA_WriteFile(fsaFd, data, size, cnt, pFile, flags);
    IOSUHAX_FSA_CloseFile(fsaFd, slcFd);
    // ... 
    Haxchi makes a nice example of writing with iosuhax :P
     
    Last edited by BullyWiiPlaza, Mar 31, 2017
    KiiWii likes this.
  5. wj44

    wj44 GBAtemp Fan

    Member
    477
    354
    Jun 18, 2015
    Gambia, The
    Of course this would be permanent. If you want temporaly you need to make fs patches like in Cafiine.