tutorial:file_operations

File Operations

In most software applications that require large sets of data or interaction which cannot be expressed before compile time, we use files to store large sets of data and other information that we may require for our programs. These files and folders can be accessed by our program in order to carry out the program's intended purpose. This could be configuration files in formats like JSON, INI, PROPERTIES, TOML, etc. or maybe textures for your games and applications or audio for music playback and so many more uses!

On the PSP, the files are stored on your memory stick. It uses the FAT file system and allows you to create, modify, and delete files pretty freely. You also have other spaces such as Flash memory which is meant for OS and system storage, and UMDs which can also store data. In this context, we're looking at just the Memory Stick. The drive name of the Memory Stick is ms0 (memory stick 0 - as if we could have a PSP with more than 1). The PSP uses Unix-styled line separators with '/' instead of Windows' '\'. The PSP file system is rather simple and easy to use and is exposed through the sceIo module. This “file manager” allows you to interact directly with the disk through Unix-like syscalls. Fortunately, C, C++, and Zig all have file abstractions through their respective standard libraries. Rust has an experimental standard library for the PSP but I have been advised to wait for it to be finished before attempting to use it. Our application will create and open a file, write some data, read it back, and then exit.

main.c

// The beginning is covered elsewhere
int main() {
    setupCallbacks();
 
    FILE* file = fopen("test.txt", "w");
    fprintf(file, "1 2 3! Hello PSP!");
    fclose(file);
 
    FILE* file2 = fopen("test.txt", "r");
    char arr[19] = {0};
    fread(arr, sizeof(char), 18, file2);
    fclose(file2);
 
    pspDebugScreenInit();
    pspDebugScreenPrintf("%s", arr);
}

main.cpp

#include <iostream>
#include <fstream>
#include <string>
 
// Rest is the same
 
auto main() -> int {
    setupCallbacks();
 
    std::ofstream file("test.txt");
    file << "1 2 3! Hello PSP!" << std::endl;
    file.close();
 
    std::ifstream file2("test.txt");
    std::string str;
    std::getline(file2, str);
    file2.close();
 
    pspDebugScreenInit();
    pspDebugScreenPrintf(str.c_str());
}

In the C example we use the C FILE* in order to open the files via fopen and uses fprintf and fread in order to work with the file handle. In C++ we have to use an ifstream and ofstream to read and write respectively. The C example also will work in C++ - and you technically only need one FILE* in C. A pure fstream does not work in this instance on the PSP. In C++ we also use the getline method to read until a newline.

main.rs

#![no_std]
#![no_main]
 
psp::module!("Tutorial", 1, 0);
 
use psp::sys::{IoOpenFlags};
use core::ffi::c_void;
 
pub fn psp_main() {
    psp::enable_home_button();
 
    unsafe {
        let fd = psp::sys::sceIoOpen(b"ms0:/PSP/GAME/Rust/test.txt\0".as_ptr(), IoOpenFlags::CREAT|IoOpenFlags::WR_ONLY, 0777 );
        psp::sys::sceIoWrite(fd, b"1 2 3! Hello PSP!\n\0".as_ptr() as *const c_void , 19);
        psp::sys::sceIoClose(fd);
 
        let fd2 = psp::sys::sceIoOpen(b"ms0:/PSP/GAME/Rust/test.txt\0".as_ptr(), IoOpenFlags::RD_ONLY, 0777 );
        let array: [u8; 19] = [0; 19];
 
        psp::sys::sceIoRead(fd, array.as_ptr() as *mut c_void, 19);
        psp::sys::sceIoClose(fd2);
 
        if let Ok(s) = core::str::from_utf8(&array[0..]) {
            psp::dprintln!("{}", s);
        }
    }
 
}

In Rust, we use the IoFileMgr module in order to work on our application. This application requires that the hardcoded path is valid. We then write to a file opened in create mode and then close it. We then reopen it in read mode and then read to our array. Finally, we derive a string from the raw utf8 and print it out to the screen.

main.zig

const psp = @import("Zig-PSP/src/psp/utils/psp.zig");
usingnamespace @import("Zig-PSP/src/psp/include/pspdisplay.zig");
const gfx = @import("gfx.zig");
pub const os = @import("Zig-PSP/src/psp/pspos.zig");
pub const panic = @import("Zig-PSP/src/psp/utils/debug.zig").panic;
const std = @import("std");
 
comptime {
    asm(psp.module_info("Zig Tutorial", 0, 1, 0));
}
 
pub fn main() !void {
    psp.utils.enableHBCB();
 
    var f = try std.fs.cwd().createFile("test.txt", .{ .truncate = true });
    var writ = f.writer();
    try writ.print("1 2 3! Hello PSP!", .{});
    f.close();
 
    var fr = try std.fs.cwd().openFile("test.txt", .{ .read = true });
    var read = f.reader();
    var buf : [20]u8 = undefined;
    var read_size = try read.readAll(buf[0..]);
    fr.close();
 
    psp.debug.screenInit();
    try psp.debug.printFormat("{}", .{buf[0..read_size]});
}

In Zig, we set up the Custom OS for STD via pub const os = @import(“pspos.zig”);. This sets up our custom OS so that Zig can support our application. Then it's just a matter of creating a file in truncate mode in order to overwrite it. Then we simply use print to print out, and read into the buffer to read our text back in. Finally, we use the printFormat method in order to create a printed string.

  • tutorial/file_operations.txt
  • Last modified: 2020/11/20 03:30
  • by iridescence