From af7c1a40d1094a263a648a2c335e0876fc6e0a24 Mon Sep 17 00:00:00 2001 From: JJ Bliss Date: Tue, 28 Apr 2026 16:22:09 -0400 Subject: [PATCH] Files much more complete. Needs a lot of cleanup though --- uxn/uxn.ha | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 263 insertions(+), 3 deletions(-) diff --git a/uxn/uxn.ha b/uxn/uxn.ha index 04b9fd3..72386dd 100644 --- a/uxn/uxn.ha +++ b/uxn/uxn.ha @@ -44,6 +44,7 @@ export type uxnFile = struct { state: filestate, dir: bool, exists: bool, + fappend: bool, }; export type filestate = enum { @@ -91,6 +92,7 @@ fn initialize_uxn_mem() *uxn = { state = filestate::IDLE, dir = false, exists = false, + fappend = false, }...], })!; }; @@ -199,12 +201,20 @@ fn emu_deo(port: u8, value: u8, state: *uxn) void = { const high = state.dev[0x90]; const low = value; state.mouse_vector = short_from_bytes(high,low); + case 0xa5 => // File/stat [0] + filestat_to_addr(0,state); + case 0xa6 => // File/delete [0] + delete_file(0,state); case 0xa9 => // File/name [0] set_file_name(0,state); case 0xad => // File/read [0] read_file_to_addr(0,state); case 0xaf => //File/write [0] write_file_from_addr(0,state); + case 0xb5 => // File/stat [1] + filestat_to_addr(1,state); + case 0xb6 => // File/delete [1] + delete_file(1,state); case 0xb9 => // File/name [1] set_file_name(1,state); case 0xbd => // File/read [1] @@ -338,6 +348,108 @@ fn read_file_to_addr(fi: u8, state: *uxn) void = { }; return; }; +fn filestat_to_addr(fi: u8, state: *uxn) void = { + const length = if(fi == 0) { yield short_from_bytes(state.dev[0xaa],state.dev[0xab]); } + else { yield short_from_bytes(state.dev[0xba],state.dev[0xbb]); }; + const destaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa4],state.dev[0xa5]); } + else { yield short_from_bytes(state.dev[0xb4],state.dev[0xb5]); }; + // fmt::printfln("Reading {} bytes to 0x{:x}", length, destaddr)!; + let bytesread = 0; + + if(!state.files[fi].dir){ + const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); } + else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); }; + const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0; + const name: str = match(strings::fromutf8(nameslice)){ + case let s: str => + yield s; + case => + fmt::fatal("Bad String encoding in filename"); + }; + const stat = os::fstat(state.files[fi].file: io::file)!; + const fsize = stat.sz: u32; + const modestring = if(fsize <= 0xFFFF){ + yield fmt::asprintf("{:x.4}",fsize)!; + }else { + yield "????"; + }; + let modestringiter = strings::iter(modestring); + for(let i: u16 =0; i + yield r; + case done => + break; + }; + bytesread += 1; + state.ram[destaddr+i] = char: u8; + }; + for(let i: u16 =0; i + bytesread += 1; + // fmt::printfln("Reading byte {}: from file",b)!; + state.ram[destaddr+i] = b; + case let eof: io::EOF => + state.ram[destaddr+i] = 0; //This might need to be done differently + case let err: io::error => + fmt::fatalf("Error reading: {}", io::strerror(err)); + }; + }; + + }else { + const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); } + else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); }; + const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0; + const name: str = match(strings::fromutf8(nameslice)){ + case let s: str => + yield s; + case => + fmt::fatal("Bad String encoding in filename"); + }; + let dfs = os::diropen(name)!; + let df = os::dirfile(dfs); + const stat = os::fstat(df)!; + const fsize = stat.sz: u32; + const modestring = if(fsize <= 0xFFFF){ + yield fmt::asprintf("{:x.4}",fsize)!; + }else { + yield "????"; + }; + let modestringiter = strings::iter(modestring); + for(let i: u16 =0; i + yield r; + case done => + break; + }; + bytesread += 1; + state.ram[destaddr+i] = char: u8; + }; + for(let i: u16 =0; i + bytesread += 1; + // fmt::printfln("Reading byte {}: from file",b)!; + state.ram[destaddr+i] = b; + case let eof: io::EOF => + state.ram[destaddr+i] = 0; //This might need to be done differently + case let err: io::error => + fmt::fatalf("Error reading: {}", io::strerror(err)); + }; + }; + }; + + if(fi == 0){ + state.dev[0xa2] = (bytesread >> 8): u8; + state.dev[0xa3] = (bytesread): u8; + }else if(fi ==1){ + state.dev[0xb2] = (bytesread >> 8): u8; + state.dev[0xb3] = (bytesread): u8; + }; + return; +}; fn write_file_from_addr(fi: u8,state: *uxn) void = { const length = if(fi == 0) { yield short_from_bytes(state.dev[0xaa],state.dev[0xab]); } else { yield short_from_bytes(state.dev[0xba],state.dev[0xbb]); }; @@ -360,17 +472,126 @@ fn write_file_from_addr(fi: u8,state: *uxn) void = { case => fmt::fatal("Bad String encoding in filename"); }; - state.files[fi].file = match (os::create(name,fs::mode::USER_RW,fs::flag::RDWR)){ + const flags = if(appendf) { + state.files[fi].fappend = true; + yield (fs::flag::RDWR | fs::flag::APPEND); + } else { + yield fs::flag::RDWR; + }; + state.files[fi].file = match (os::create(name,fs::mode::USER_RW,flags)){ case let f: io::file => + state.files[fi].exists = true; yield f; case let err: fs::error => fmt::fatalf("Error creating file file: {}", fs::strerror(err)); }; }; - if(appendf){ + + if(appendf && (state.files[fi].fappend == false)){ //Rewrite File or adjust pointer - yield; + io::close(state.files[fi].file)!; + + const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); } + else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); }; + const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0; + const name: str = match(strings::fromutf8(nameslice)){ + case let s: str => + yield s; + case => + fmt::fatal("Bad String encoding in filename"); + }; + state.files[fi].file = match (os::open(name,fs::flag::RDWR | fs::flag::APPEND)) { + case let file: io::file => + state.files[fi].exists = true; + state.files[fi].dir = false; + yield file; + case let err: fs::error => + //Try opening as dir + yield match(os::diropen(name)){ + case let f: *fs::fs => + let dfs = os::diropen(name)!; + let df = os::dirfile(dfs); + const stat = os::fstat(df)!; + state.files[fi].dir = fs::isdir(stat.mode); + if(state.files[fi].dir) { + state.files[fi].exists = true; + state.files[fi].dir = true; + + }; + io::close(df)!; + yield io::empty; + case let err: fs::error => + // fmt::printfln("Error opening {}: {}", name, fs::strerror(err))!; + if(fi == 0){ + state.dev[0xa2] = 0; + state.dev[0xa3] = 0; + }else if(fi ==1){ + state.dev[0xb2] = 0; + state.dev[0xb3] = 0; + }; + state.files[fi].exists = false; + state.files[fi].dir = false; + yield io::empty; + + }; + + + }; + state.files[fi].fappend = true; + }; + + if(!appendf && (state.files[fi].fappend == true)){ + + io::close(state.files[fi].file)!; + + const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); } + else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); }; + const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0; + const name: str = match(strings::fromutf8(nameslice)){ + case let s: str => + yield s; + case => + fmt::fatal("Bad String encoding in filename"); + }; + state.files[fi].file = match (os::open(name,fs::flag::RDWR)) { + case let file: io::file => + state.files[fi].exists = true; + state.files[fi].dir = false; + yield file; + case let err: fs::error => + //Try opening as dir + yield match(os::diropen(name)){ + case let f: *fs::fs => + let dfs = os::diropen(name)!; + let df = os::dirfile(dfs); + const stat = os::fstat(df)!; + state.files[fi].dir = fs::isdir(stat.mode); + if(state.files[fi].dir) { + state.files[fi].exists = true; + state.files[fi].dir = true; + + }; + io::close(df)!; + yield io::empty; + case let err: fs::error => + // fmt::printfln("Error opening {}: {}", name, fs::strerror(err))!; + if(fi == 0){ + state.dev[0xa2] = 0; + state.dev[0xa3] = 0; + }else if(fi ==1){ + state.dev[0xb2] = 0; + state.dev[0xb3] = 0; + }; + state.files[fi].exists = false; + state.files[fi].dir = false; + yield io::empty; + + }; + + + }; + state.files[fi].fappend = false; }; match (io::write(state.files[fi].file,bytes)){ @@ -402,6 +623,7 @@ fn set_file_name(fi: u8, state: *uxn) void = { }; io::close(state.files[fi].file)!; + state.files[fi].fappend = false; // fmt::printfln("Opening file: {}",name)!; state.files[fi].file = match (os::open(name,fs::flag::RDWR)) { case let file: io::file => @@ -443,6 +665,44 @@ fn set_file_name(fi: u8, state: *uxn) void = { return; }; +fn delete_file(fi: u8, state: *uxn) void = { + let success: u8 = 0; + if(state.files[fi].exists){ + const nameaddr = if(fi == 0) { yield short_from_bytes(state.dev[0xa8],state.dev[0xa9]); } + else { yield short_from_bytes(state.dev[0xb8],state.dev[0xb9]); }; + const nameslice: []u8 = bytes::cut(state.ram[nameaddr..],0x00).0; + const name: str = match(strings::fromutf8(nameslice)){ + case let s: str => + yield s; + case => + fmt::fatal("Bad String encoding in filename"); + }; + if(!state.files[fi].dir){ + match(os::remove(name)) { + case void => + success = 1; + yield; + case let err: fs::error => + fmt::printf("Error deleting file: {}", fs::strerror(err))!; + }; + }else{ + match(os::rmdir(name)) { + case void => + success = 1; + yield; + case let err: fs::error => + fmt::printf("Error deleting dir: {}", fs::strerror(err))!; + }; + }; + }; + if(fi == 0){ + state.dev[0xa2] = 0; + state.dev[0xa3] = success; + }else if(fi ==1){ + state.dev[0xb2] = 0; + state.dev[0xb3] = success; + }; +}; fn deo_expansion(addr: u16, state: *uxn) void = { const op = state.ram[addr];