 Okay, day seven. So last time I got tight working, but only once, then you would have to reset everything before it would work again. And I figured out what the problem was, and it was nigh trivial. When I was pausing the FCB for the parameter, I was forgetting to clear the current record field. So after going through once, that field would end up being set to one, indicating that the first record had been read. So then go through again and parse it, and I think it was at the end of the file. If you remember correctly, this is what actually happened before when reading the CCP from the BDOS. So that's really annoying. So what I've actually done is I've created some helper tools, which do such, which which wrap the BDOS functions, and this will clear the FCB properly. Where did I put it? Open.sxfcb open. There's nothing to these. This one just loads the FCB pointer into a variable. This one wipes it. Then we call the BDOS. There's a handful of these. They also do things like get user codes right, which is irritating in the BDOS. So anyway, I can now type readme.txt as often as I like. So let's try and get dear done, which is the most common CCP command. So that would go here. And in the 8080 code, this is this routine called direct. Not quite sure why they didn't call it dear, but it's not that long. All it does is use findfirst and findnext to scan the directory and prints the result. So the first thing we're going to do is of course print a new line, pause it as before. We can probably common out some of this, but I'm not going to do it just yet. If the user specifies just a drive or nothing at all, then we want to... Actually, if the user specifies nothing at all, then this is going to fail here. I think we actually want to... If the user specifies nothing at all, then we bail out here. I actually... With an error, I actually think that... Wait a minute. Come on, line plus one. Oh yeah, that's the first character. I think we're actually going to exit with carry clear so that an empty parameter, this is the end of the line, pauses successfully into an empty FCB. So we then go look at the first filename character, which is at F1. Okay, is it a space? Then fill it with equal signs, F1 until negative. If empty FCB, fill with question marks. Okay, set disk is being called. This changes the current drive. It clears the disk byte from the FCB Do nothing if not specified. Otherwise call the BDOS to set the disk. Why do we not just use the drive letter in the FCB? So we're actually going to be calling find first, find next. Again, this is... This will be familiar if you're used to DOS. If you match anything on the disk, if the first byte of the FCB... Right, this is only ever scans the current drive. Not sure what happens if you tell it to scan a different drive, but... Okay, let's do this. So load the drive byte. If it is not zero, then we're going to set the drive. Actually, did I remember to change... Yep, that's set. What did I call that? Was it select disk by any chance? Yeah, I think it was. Or we want to set the current drive. So are we going to rely on the BDOS to remember what drive we're on? Or are we going to use our own idea? I think we're going to do this. So even though the BDOS is remembering what the current drive is, we're actually going to remember this ourselves so that... I'm thinking so that we can just change the drive to whatever one we have to work on and not have to worry about things, but... Yeah, yeah, let's do that. So we're going to move this call to get drive to here and store it in drive. So this just becomes LDA drive. So inside dear, we want to either use the drive from the FCB or the current drive. So if we do it like this, we say fetch the drive in the FCB. If it is zero, fetch the current drive and then just set it and even build. And then we start... Well, then we actually start scanning the directory. So we load the FCB. We call BDOS find first. Apparently I spelled that with a underscore. So this may immediately fail. So if carry is clear, then we loop. We are going to print the directory here. Then we are going to find the next item and keep going until we run out of files. So at this point, we should have a directory item. We are going to print it. Currently, I'm going to do this in a really dumb way. So we're going to just go with one, repeat, load, hang on a second. How does find first and how find next work? DE is the address of the FCB. It uses the DMA address to return the resulting directory look buffer. Okay. So we're going to have to set that to... And we need a 128 byte buffer. So we're going to use the command line buffer for that because it's the right size. Okay. So this will write a directory item into our buffer. We now want to print it. So get offset of the directory item. So we want to multiply a by 32 on return from find first, find next. You get the offset 0 to 3 in the record. And each record can hold four directory entries. So we want to shift that left by 2. ASL, A, ASL, A. Put that into X. Print the directory item. So load command line by X plus 1 by X, call BDOS con out. But of course, this will lose that parameter. Okay. So print 11 characters. Then we print a new line. Get directory entry. Okay. And we are actually going to change this code to be a lot nicer. This is just for now. Does that assemble? No, it doesn't. Z until CS. Done one of them. Okay. We do not have BIOS find first and BIOS find next. Find first and find next. And it works. Okay. So now we can run it. And we type DIR and press return. Good. That means that we hit a unimplemented BDOS call. So this is going to be probably select disk. Okay. It's printed in A. So we have in fact hit the first one, which is this one, select disk. So actually we have already implemented this one. We just don't have a externally facing entry point for it. Where did I? There it is. Entry, select disk. And this goes down in the drive section in here. So the select disk is this one. E is the drive number, which is 0 based. I need to adjust the CCP for that. So I think that all we do is, so we pass the parameter and we just call login drive. Wait a minute. This should be, this should be an internal. And we can also do a entry login drive. So that whole thing is now only three bytes. Okay. Let's adjust the CCP. Set the drive. Well, the drive might be, the drive might be 0. Okay. We're going to have to put it into an index register so that we can cheaply decrement it. If it goes negative, then we load the real drive, which is going to be 0 based. Then put x into a to call select disk. All right. We've got all the way to c. That was a. That was b. We've just called find first c. And we actually have nearly all of find first already because we're using those for directory scanning an open file. We've got find first here and we don't have a find next. We've got find next here. So the interface is actually kind of different from find first and find next. What are we actually comparing against this? Param. Okay. We do have an FCB in in param, but we want to find first is function 17. So here is the CPM 80 version that's doing. Okay. We are going to call new user FCB. We'll convert the FCB clear the s2 byte, which is what we want, but not the extent byte weirdly. And we're now should be ready to scan the directory. If the if the drive field is question mark, then do not change the wait a minute. Right. This is special. If the if the drive field is a question mark, then we do not do a normal directory scan. What we do is we just fetch everything for the current drive. So new user FCB will read the drive byte and select the current drive. So here we actually want to check to see if the first byte is a question mark and do two different code paths depending. So compare it with a question mark. If it is a question mark, then we're going to search for everything. If it's not, then we are going to select the drive where we're going to call new user FCB, which will prepare the FCB and then do a normal scan. So this is a normal search. So it's putting a question mark in the it's fetching the extent byte. Is it a question mark? If so, clear the right. What this is doing is you can put a question mark in the extent byte if you want to get all directory entries for all extents. And if you do do that, it also clears the module number so that you only ever see the first module, which is odd anyway. And the module number is in s1 I believe, s2. So that is this one, s2, yep. Okay. And this has in fact done everything. In fact, calling new user FCB is going to clear the module number. In this code, if do you want to do that? What does re-select do? Re-selects the current FCB. So if you don't have a question mark in the extent byte, you can have a question mark in the module byte to receive all modules on the disk. So in fact, a new user FCB code here that clears the s2 byte and falls through to convert user FCB, we don't want. Okay. So this should be convert user FCB. Okay. If we are doing one of the special wildcard searches, then we want to do nothing, I think. So convert user FCB is also, well, select active drive is, it does decide if the drive was already logged in. If we are logged in, finish. Where do we merge in the user byte? Have I done that code? Yeah. Convert user FCB. Okay. So this is actually pretty irritating. In our, if it's a special wildcard search, then we can't call convert user FCB because the drive byte is not a drive byte. So we are actually going to have to do the conversion ourselves, but that's actually straightforward, like so. I think we may want to call, I think we do want to call re-select. Now we don't. Okay. We also want to, we also want to set the number of bytes we want to compare in the FCB. In this case, in the 8080 code, it's setting namlem, which is 15. So we then call, find first, sets current drent to the appropriate directory entry. We now want to subtract current drent from user dma. No, we want to, we want to subtract current drent from the drent buffer if it's successful and put that in param plus zero. And I don't think we care about the other byte. We know that current drent is always going to be within 256 of directory buffer or within 128 rather. So I think that will work just fine. And in fact, we want to lsra, lsra. Okay. And this gives us the correct return parameter, except there's one important thing we haven't done, which is that we have to actually copy the directory buffer into the dma buffer. So we always copy the entire sector and then return a pointer to the appropriate bit. Okay. Entry find next is undefined. We haven't done that one yet. Range error in 803. User dma is a, it isn't a pointer. Okay. So let's just see what happens, shall we? Nothing. I was expecting more, you know, something like at least a decent crash. All right. And we are at 1cb1, which should be here. So set pc to 1cb1234. We're here. So fcb is at 249b and consists of a question mark and lots of spaces, which is wrong. This will explain why, this will explain why that's not working. I mean, I believe it has successfully done find first, not found anything, and exited. So is this just the drive? Yeah, if so, right. We're here. 249b. There's our fcb. There's our fcb. Unspecified drive. Lots of question marks. In fact, there are one too many question marks. That should be a 10. Oh, something, it did something. It said vyd find next, which is wrong. I mean, it looks like it got something, which is garbage, and then hit find next. But let's try this one more time. 249b. Okay, question marks. We have four bytes of metadata at the end, which is zero. So is the drive byte a question mark? No, it's not. We go to here. We convert the fcb, which will also select the drive. And now if we look at 249b, you can see it hasn't changed as we expect. We now want to check the extent byte. Is it a question mark? It is not. Therefore, the number of bytes you want to search is 15. So we're going to look for a matching, any matching directory entries that match this fcb, ignoring that byte, which is s1, which is unused. So go to 1d1. Okay, we are now in find first in here. So a is the right value. We store that in find first count, home drive, reset directory position, read a directory entry into the directory buffer, check to see if we've run out at the end, which we haven't. Does the user want to see deleted files? See, I would expect this to be the case if they, if they set a question mark, any directory entry will match, but not delete files, not deleted files. Okay. But anyway, it's not an e5. So we're here. If the current drent is higher than cdrmax, then the rest of the directory entry is empty, which it's not. We are here. So we're actually now going to compare the directory buffer, which is, I don't know where it is. Anyway, we're now looking at the drive byte. Yeah, this is a wild card. It's not. Are we on byte 13 s1? No. Are we on the extent byte? No, we're here. Current drent is at 0615. So we're now looking for ccp.sys. Okay. And we're checking to see if this matches our all question marks, FCB, except for the drive byte, which is zero. I suspect that our prepare FCB routine is not put storing the drive byte here. I mean, it's all zero now, so it'll work because you only have one drive. Anyway, I think this is actually going to all work. This is the same code we were using elsewhere. So we let's break at 1d678. Okay, we got something. We have gone up to here. We are now copying the directory sector add to pointer. That's why it doesn't work. Well, it still doesn't work, but that was wrong. So our directory buffer is 0615. So yes, there is a sector. So load a byte. We store it into our user DMA area, which is at 23FA. And there it contains, this is the command line that we used last time. So that's correct. This is relic data from last time. So we're actually just going around storing lots of stuff. So yeah, we're starting to write E5. So let's break at 1cf234. Continue. Right. So now if we do that, there is the directory sector that we've copied. Load current DRN to plus zero, which is 1.5. Subtract the low byte of the directory buffer. This gives us OD. That's not a factor of directory buffer as a pointer. There we go. ccp something sit. That s will be because of this should be, no, no, this should, this should be, wait a minute, how is this even working? We're not incrementing x, you see. So something must be incrementing x. That's weird. Anyway, this code is garbage. We want to, well, I don't know where the P went. That's strange. Let's go back to the BDOS and do find next. So this is funk 18. See, I would expect this to, yeah, the only way find, the only way funk 18 here will work as it stands is if we don't convert the fcb back again. In fact, I think this is not, wait a minute, the, okay, yes, this is converting the fcb, but not in the same way. So when we actually compare again, let me do that again, the directory entries on disk contain the user code and the drive byte. So we don't want to load the current drive, we just want to load current user and we stick that in the fcb drive byte there. Here in convert user fcb, we are extracting the drive byte and setting it. And this is wrong. We just again, we just want to load the current user and stash it in the fcb and select the drive that was in the fcb. Okay. So, but this code over here in cpm 80 is not checked what reselect does. No, I'm sorry, this is actually doing the, yeah, then I wrote this using the other version with all the different names. So, yes, this is, this is actually going to be doing a conversion. So we're going to want to basically do this code again. So let's just do a helper. Okay. So this then becomes set up fcb for find, but this time we call find next and get rid of that. And we are going to want to copy this as well, including the carry. Well, that's not really what I was expecting. So what this is, the result is that this value is garbage. Yeah, this should go in our param return parameter. No, it doesn't. That's because I actually eliminated our param. And the return code is now just returned. So actually, that should have worked. That's not right. So one CEC. Okay. So first time through, we copy the stuff. We do the thing. This is the code I'm interested in 1d 01. Okay, so we load the current directory. That's a pointer. But it's still not right. Wait, we don't know what am I doing? We don't want to dereference the pointer. We want to read it. Okay, it's still not working. Right. First time through. So we have one five. And we are subtracting one five. Okay, that's that's right. So this gives us a zero, which we shift right to produce zero. Second time through, we have a three five from which we subtract a one five, giving us two zero, which we then shift right twice, which gives us entirely the wrong number. We want to shift this by five for five bytes. It's not worth doing a loop. Okay, first time. Ah, okay, I have to consider that I've also got this wrong here. That should be shifted left by five, but it's still not helping. Because I am using the same index for the counter and the offset. So I think what I actually want to do, what do I want to do? I am going to zero terminate that string by putting a zero in the expite, which we don't actually care about for just listing the directory. Then then I'm going to add a on to command line. But we actually want to increment increment y by one so that this pointer is actually pointing at the first character of the string. We want to leave the result in a x like that. So we can just call Edos right string to print it. So it's got the second and third one right, but not the first one. So a is zero, meaning the first directory entry. This is the one that went wrong. So we go through the exit sequence. We are here. We have a success. We are now shifting a left by five. If we look at 240023fo, we do actually see that ccp sys is there. 23fvd cb. There we go. There is our sector lined up. So this should be writing a zero. 23fb. There is our zero there. Yny, tya, add 23fb. So ax is showing 23fc. So step, it prints a c. Why does it just print one c? Okay, we're now here again. So call the bedos. Stash our string in pointer at 04. So we see 23fc there. And there is the string we want to print. Set up the call. Okay, we're here. Get a byte, which is a 43. Yeah, that's a c. Is it a dollar sign? No. Print it. There's a c. Increment param zero to fd. We can't do that. So that's testing if it's negative. I actually want to test if it's equal as is it's rolled round to zero. That'll be why. There we go. There are our three files. Awesome. So dir is now working. We just need to fix the formatting. So how is this doing it? In fact, we're doing a little bit more than that. We're checking to see if it's a system file and ignoring it if it is. System files work by using file attributes, which are bit set in the top bit of the file name. So system is hidden and it's in t2. So what we're going to do is if it's zero, then it's not a system file. So go around again. Well, if it's not a system file, we print it. Okay. So this should work, but we don't actually have any system files. In fact, we should be able to do it by cpmchatter by saying that ccp.sys, which you do want to be a system file, is a system file like that. Okay. So now if we do a dir, we don't see it. Good. And in fact, I am not going to do that as a matter of course, because I would actually kind of like to be able to see the ccp file. So what else is this doing? Oh, yes. There's header stuff. Right. It counts the number of files so that it can put four files on a line. And it also puts the directory, the drive on. So what's this doing? And it also turns the space separated format into a more traditional dot format. Okay. So temp plus zero is the file counter. If it's a zero, then print new line. And in fact, we're going to store the offset into the directory buffer, because this call is going to wipe that register. So it's so e is the counter. Right. It puts e into a increments e, then does the test. So we have to do it a bit differently. So we want to do a post increment. So in fact, this will work just fine. No, it won't because the ink is going to set flags. T x a inks 10 for zero, then and the thing that we had in a. Okay. You know what, rather than decipher what this does, I'm actually just going to go and find a listing of what dir does in CPM and rework that from first principles. Here is an extremely purple copy screenshot from CPM three on the Commodore 128 showing how it works. Okay. So that's actually reasonably straightforward. We're going to lose the new line at the beginning of the dir, because this one will do it. So at this point, we wish to print the current drive. And I can't remember what we decided for drives earlier. Right, we want to, we want to show the drive that we actually used. So that will be the one that the BDOS knows about. So that's BDOS, BDOS get to drive. So you'll see ADC, A, JSR, BDOS, con out. Okay. We now want to print the header, which goes before each file. Yeah, I can't type anymore. Okay. In fact, this doesn't print this with dots, which is very convenient, because now we can use the same code that we had here. So we want to load 10 plus one, which is our directory offset into Y, do the print. And then at the end, print a space. And we are just going to, and at the end, do we want to print a new line? It doesn't look like it is, actually. So let's see what this does. There shouldn't be a new line after the trace. It's, that should be an STX. It hasn't incremented it. That's why. Okay. ccp.sys. Ooh, it's being clever. There's an extra space between the file name part and the extension part. So rename.com has got the same size of file name as readme.txt. And you see there's three spaces there, but there's only two there. So that's actually annoying. So what we are actually going to have to do is, if I can find my dir again, clear file counter here, we are going to want to actually just compute the address. So this then becomes fcbet2.temp.y. The line header stuff, we're actually reading this from file counter. And the only reason we put this into X is so that we can use inks there without affecting that. Yeah, it's a little bit miserable. I much prefer working with a 65co2 with all the extra instructions. It's so much more orthogonal. Okay, we are going to have to make a helper which writes a string with a given length. And of course, we can't use any of temp because we're using them all. No, let's not do it like that actually. Not using temp plus three. So this is going to be a loop which pre-increments the pointer. So the pointer is now pointing at the file name. Load the byte, print the byte. This is going to be our counter. Deck zero page sets zero. Okay, deck index until zero. Okay, then we print the trailing space. And there's a reason why we want to get the format exactly right. Well, the reason is because that should make the text line up. But I'm not sure it does so we've got 9, 10, 11, 12, 13, 14, 15 characters from there to there, which is 30 to there. No, it doesn't line up. I for some reason I thought it did. I thought the layout was such that, yeah, this is five across. Wait, five across? Really? One, two, three, four, five. But the code we've been looking at clearly wants to do four across. New line of mod four equals zero. I think the CCP, this is four across. But it's the same format. Two, three, four, yeah, nine there. You see, I was thinking that there would be a dividing line down the middle. And it would neatly wrap on a 40 column display. But now I look at it, it clearly doesn't. Otherwise, it would have to draw the drive letter again over here. Well, that's garbage. I'm going to be running this a lot on 40 column systems. So what can we do? Well, we've got 8, 9, 10, 11, 12. So two of those is 24. Well, if we put an extra character for a separator, we get 26. Three is 39. 40 divides into 10, which isn't enough. What are the factors of 40? 20, which is not enough. 10, which is too small. Honestly, I think what I'm going to do is just change that. So this gives us columns of two, which if I go into a 80 column mode looks stupid. But I think I will deal with that later. I think I need a new line at the end as well. Okay, well, that's not very good, but it works. We have a working DIR. And with luck, wildcards will work. So if we do dirrstar.star, we see readme. If I do star.com, we get just the com files. If I do star.sys, we get just the sys files. I can do dir a colon and get drive a. If I do dirb colon, I still get drive a. That should have produced some sort of error. There's very little error handling in this. So it's possible it has actually failed with an error. And then that's just kept going. Yeah, this should have failed here and just done nothing. So find first. So I gave it drive b. There should have been a drive. It should have gone through convert. So it should have selected drive one. But there is no drive one. So I think I'm going to turn these into do that branch of carry set to find error. We want to do that again there, there and there. Find error is just going to return with carry set. What does this font search for first returns a equals ff if error or zero to three if successful. So the only error therefore is we could put an error in x if you wanted to be CPM three ish. But carry is set. So that will do for now. This I believe should be checking for errors branch of carry set to error. There we go. CPMs error handling is not great. But okay, I believe that we now have we just make sure it's still doesn't work here at all. Right, I bet convert user FCB is yes, select active drive is the thing that should be checking for errors. So we call the BIOS if carry is clear, we do this if carry is set then we exit. So I suspect BIOS is indeed setting the carry bit on exit. So why is that yeah, that was just clean up that didn't do anything. Okay, well the last thing we did was to put something in here. So we call the BIOS if carry is clear, then we do this. Okay, we're getting yeses. So I assume that's working. Ah, we want to set clear carry there. There we go. And star. Yep. And star star. Yep. So this will find the first file that matches the wild card. That's intentional, I think. But yes, okay, dir works, we can use it. So we can type, we can dir. The other CCP commands are arrays, save, rename and user. Let's bash user out because that's easy. User just changes the current user code. So I don't want to put that there. Yeah, we want to put that here. This, here we go, get number. Oh, good heavens. This actually does it by passing the number into an FCB because at least that means that all the digits are present, are all at fixed addresses. That's pretty cool. Okay, let's do that then. So we want to put this in user FCB, modx, user FCB, jsrparse, FCB. If carry is set, then this is invalid. So we now want to, do we actually want to do it like this? Because it's doing it, the 8080 code is doing it like that because the 8080 is very bad at indexing. You have to do it all with pointer arithmetic, but we're on the 6502. We are better at indexing. So I, now I think about it, I think we can, okay, so we are going to load, load a byte. If it's zero, exit. So is it less than a zero? Carry is clear. This means that it's invalid. So if carry clear is an error, if it's a space, we exit. If it is greater than or equal to nine plus one, carry is set, then this is an error. It's not a valid number. Okay. So we now know it's a digit. So subtract to get the actual number, dash it. We now want to multiply this by 10, the existing number. To do that, 10 is in binary, it's 1010. So if we shift left by one bit, s, l, a, that's multiplied by two. Yeah. This 1010 means that 10 is four plus two. So multiplying by 10 is the value multiplied by four plus the value multiplied by two. Well, that has multiplied by 10. Store it multiple, sorry, not multiplied by four, multiplied by eight, multiplied by four, multiplied by eight, add the value multiplied by two that we computed earlier. Hang on, multiply by two, four, eight. Yeah, okay. This should give us the value by 10 and store. Fetch our value here, add on to the existing one, store. And in fact, we don't want to stash that in x. We want to stash that in y, inks, loop. Okay. So user just becomes parse number. If carry is set, it's an error. We have the number in a. So we are just going to set it. I did not put a entry point in here. Get set, user, and put a new line in. So run, dir, we see files, user one, dir, we still see files, we shouldn't be. That should have set us to user one and all our files are in user zero, user, g, bad number. I think that hang the weird behavior that the original CPM, CCP has with new lines. We are going to put a new line in immediately after we read the line. We just do a new line there. So now we can dispense with the various new lines we've inserted. We do get an extra new line there. Sadly, user zero, user one. So gets the implementation of get set user is uses the same call to do both. If the input parameter is ff, it returns the current user. Otherwise, it sets it. And there's not really a lot here. So I'm going to guess that the problem we're having with scanning the directory is this sort of thing. So this is we're resulting in convert, we're relying on convert user FCB. So this should be pulling the current user value out stashing it in the FCB. And then we go on to do the directory. So my best guess is that there's something wrong in parse number here. So that's just debug our way through that. Okay, user one. Skip white space. Our counter, our accumulator is zero goes in one C, load the command offset, load a bite, which is three one that is a one digit. So was it zero? No. Is it a space? No. Is it less than zero? No. Is it greater than nine? No. Subtract three zero from it. Gives us one, the value of our digit. Ta y shift our accumulator. It's all zeros. So that doesn't matter. Add on our saved value, which is now an A. So add there, store increment back to the top load our character, which is a zero. We've reached the end of the line. Exit. Yep, clear. We are here. We're now calling the BDOS function 20, go through entry sequence. We are here. So we load, we load our new user number, which is zero. Why is that zero? See on exit, our accumulator should be, we are exiting from here. So I actually want to do this. User one. A is three one. That's not right. Oh, hang on. We're in a different place. There we go. Nothing back to user zero. Good. And we're going to do one more enhancement in the CCP, which is here where we print the prompt. We are going to fetch the user number. If it's not a zero, then we print it. So user one. Now it says one A to tell us that we're in user one. User zero. There are our files. Okay. So just wondering if we can get rid of that leading new line that's printed by this because we print a new line before each filename. I think if we were to move or we want to print the header there, if we do that, so we're now printing the header at the beginning of lines and the footer at the end. We have not printed a new line there. That's because that needs to be a one. We should put that in a external constant. There we go. That's much better. All right. So user now works. The other CCP commands are arrays, rename, and the all important transient, which loads and runs an external program. Both of all three of those, well, transient requires loading the file into the TPA and currently our CCP is right down the bottom of memory rather than right up the top. So I don't really want to do that just yet until I figured out how to load programs, load the CCP at the top of memory. Arrays and rename both involve modifying the file system and that's a pleasant pile of worms. So the, where's our, here we go. This is the 8080 code close, locate a directory element and rewrite it. And there's a fair chunk of it. What it does is it scans the directory looking for exactly the directory entry in the FCB. We should see a call to call search looking for all 15 bytes, 15, uh, one five, rather, that would take us to there. So yes, not including the record count because the record count will have changed if the user has written to the FCB. Once it's found it, it then copies the data in the FCB into the directory entry, making sure to update the bitmap in memory as it goes and then writes that sector back to disk. That's all you have to do when closing files. All the metadata is in the FCB. Erasing and renaming files actually works a little differently. To erase a file, you actually scan the directory. You find all directory entries that match your file name and you wipe them, updating the bitmap as it goes because of course the file may be made up of multiple directory entries, likewise for renames. So actually that doesn't sound too bad. I mean, there's quite a lot of it. This is all cunning stuff to do with merging the data in the FCB with the one on disk. However, I think we'll do something else instead. So save. Save is a CCP built-in that's supposed to write memory out to disk. On CPMAT, you give it a file name and in the number of pages and it starts writing from 0100 to wherever you ask for. This is not very useful for us because our programs don't load at a fixed address. The point of save is that the early linkers would leave your program in memory for you to manually save or patch in other ways. But I don't think I've ever used it. So actually I am going to turn this into a different command that is more useful for us, which is called free. And what this does is it will show the current memory use. That free is a... yeah. So entries save. This becomes free. This becomes free. So we're going to go for an additional helper, which I'm going to steal from the BIOS. So I wrote that the other day, which is this, which prints a hex number. This is not in use by the BIOS anymore. But I left it in because I knew it would come in handy. This is print number hex number. It's an 8 bit hex number in A. So what it does is it saves the number you give it. It shifts it right by 4. It then prints it as a hex nibble. It returns back to here where we fetch the byte before we shifted it and fall through into H4 again to print the second one. So some strings that we'll need. So write the first message. So these memory allocations are owned by the BIOS. And the CCP can't call the BIOS. It doesn't know where it is. So normally on CPM80, programs can find the BIOS by following a couple of pointers through the BDOS. And they know where the BDOS is because it pokes that address into a zero page. But we work differently. So I'm going to do something else. I'm going to add a BDOS call to find the BIOS entry point. I don't want to reuse any of these. I mean, some of these numbers are no longer used by us like the AUX input and output. So I'm going to have to put some more in. Luckily, 38 and 39, these two, access drive and free drive, are used by CPM3 and above only. They're not used by CPM2.2. So these two slots in the table are empty. So we can add one without making our table bigger. So get BIOS. This is here. We want to maintain source code compatibility with CPM2.2. If you can find any CPM programs written in the high level language. BIOS, this will be, where did we put the BIOS? It is at location BIOS. Okay. So in the CCP, we then do a BDOS at BIOS. Did you know we won't? We then go and steal this. Okay. So we call getTPA. This gives us the base in A and the top in X. We want to save X and print the number in A then and we print two. We load this value we saved. We print it. We want to store both of those. We now want to fetch the top, subtract the base and print a new line. BDOS getTPA. BIOS getTPA. Okay. So that tells us that we have, well, these bytes for zero page are free and the total amount is this. So we then again do the whole thing. No, wait a minute. That should be getZP for zero page. But we then repeat the whole thing but differently for the TPA and all these values are in pages. So we just stick double zeros on the end of everything to turn them into actual hex values. So zero page, one nine to nine zero. One nine is the top of the BDOS. That's using a lot of zero page. See I changed the BIOS to use just one pointer which is used as an actual pointer. So we do need actual pointer but the BDOS, some of it's used for the debugging thing that uses four bytes. So two, four, six, eight, A, C, E, one o, one four, one five, one seven, nine. I think I've miscounted there because there should have come to one seven. The TPA is where the actual program goes. This is less critical. It is above the the BDOS loads at one nine zero. So this is above that. This is where the CCP has loaded and we get this much free up to 7COO. Now if I go to a 80 by 25 screen mode, you see that has used a ton more memory. It's the advantage of using the OS system calls to figure out what all these things are. So we get much less. And if I go to the 80 by 32 screen mode, we get subtle. If, however, I switch computer, I hope this will work to the Master128. Master128 has the ability to move all its video memory off into a separate memory bank so it doesn't use internal memory. And it's also got yet more separate memory to put all the file system stuff in. Whoops, that actually loaded the version of disk. The virtual file system is not working. Anyway, high mem is at 800. So no video memory on the system at all. And yeah, and base is at E00. So this would give us loads more memory. But I can't demonstrate because that needs work. But I can demonstrate. If I go back to the BBC B and turn on the tube, the tube is the is acorns twin processor system. So we're still on a BBC micro, but the BBC micro is now being used as the IO processor for a completely separate computer. And they talk to each other via an IPC interface, and it still uses all the same mass system calls. So page is even lower because the computer we're currently using, the second processor, doesn't have any file systems in it. And high mem is, well, it's 800 because we're using the basic ROM which loads at 800. But in fact, we get memory all the way up to F E O O, I believe. So let's just run DPM do free. And these numbers don't make a whole lot of sense, to be honest. In fact, we also get zero page all the way up to about E zero. But that seems to be wrong. That seems to be right. So O four O O is where the language workspace starts. Okay, I'll have to debug this. Some operating system calls return unexpected items on a tube copro. I suspect that's what I'm running into here. But this system will be able to give us a CPM system with a TPA stretching from all the way from about O eight O O once the BIOS and the BDoS have loaded all the way up to the top of memory. So you'll get a good like 50 K or so, which is pretty nice. Anyway, I'll turn that off and go back to the one that is easy to debug. Okay. All right. Well, next time I will tackle next time I think I will tackle transient commands and I will think about how to load the CCP high. There's various ways we can do it of various degrees of niceness. But now that we have a few actual useful commands running, I think that's made decent progress. So next time then.