 Day three Let's see if we can get this bitmap thing done so last time we Put the code in to scan the directory of the CPM file system so Now we actually go we are actually going to need to look at the directory entries to build the bitmap We don't actually need this for loading files, but we will need it for later. So we'll just go and do it now so we're going to add a pointer variable in zero page that will point at the Actually, I'm not going to call it FCB. I'm going to call it durant. I Will talk about FCB's later And this is going to point at the The dear directory entry that we load from disk as part of this so We do this here This will give us a pointer that we can use to examine the directory entry so a contains the offset Into the sector Directory buffer is a pointer to the directory buffer so we want to add the two together and write that to Did I call it dear end? Let's make this current dear end Current in a lot of places Current dear end. Okay So now in this code we read disk This is inside bitmap here on the left. We are going to Ignore a lot of this We do need to check to see whether this is a If this is directory entry is actually used if it is an e5 then we know that This directory entry is not in use. Okay Now we have also Something I did since last time is I added a macro that prints debug messages So we can just do this. He won't disturb any of the registers So now we can run the thing And we should see that message twice There we go because there are two valid directory entries So now that we have a valid directory entry We then call another routine to Actually update the bitmap What this will do is it'll walk through the block allocation inside the dear end and Update or clear any of the bits in the bitmap this corresponds to set file here So this will go here This is a separate This is a separate Routine because we're actually going to be using this elsewhere And I actually think we want this to be X So this in the CPM 80 is called set file here so this gets the Address of the dear end which is calling an FCB here and that is incorrect. It's not an FCB I'll talk about FCBs later adds on 16 this gets the address of the The allocation map it then Reads them one by one and They can be either One byte wide or two bytes wide depending if this is a big disk and if the block is used it will then Do the thing Which in which this case is to either set or reset a bit in the bitmap so this is where the blocks start if We are a we now need to check to see if we're a big disk Which yesterday we decided we were going to do with? By loading the High byte of blocks on disk so that if that value is zero We are a small disk if it is one we are a big disk so if we If it is sorry if it's zero or not zero so if it is not zero otherwise We want to Load to the block number Do you think we want to load the block number into? It's 16 bit block number we want to load it into a X we have run out of registers So let's store This flag In plus zero So if it's a big disk Then we want to load the low byte Increase Y load the high byte increase Y and Fall through to the routine that actually checks the block and We can't do that because We don't have this addressing mode So we're actually going to have to do more stuff So we're using 10 plus here and 10 plus one to store the set free flag and the current offset So for every block we want to Let's not do that just yet. Okay, that should do it Do a bit of stack juggling here We Pull the low byte stick it on the stack Pull the high byte Put that in X Pull the low byte off the stack We have We are very limited as to what we can do with in directions. Okay. Well that compiles We now want to check to see if the block that we've got is zero Because we know that if it's zero, it's in use it's it's not in use so that's in a and X There's not we can't do arithmetic on those So we're going to have to stash them both values Into Zero page so we can work on them and in fact if we're going to do that Then we might as well just to each store here takes two bytes the push here was one byte and this And juggling is to Let's just do it like this easier and in fact we can leave the the high byte in a So we've got the high byte in a we want to Or it with the low byte if it is zero then Continue on with the loop. This needs to be a Y And here we want to say if the offset is 32 Then we have run out of blocks and exit So we actually want to do this so Every time we hit this line here. We have a Allocated block that we want to do something with So you can put a debug statement in And run it we see two blocks That is exactly what I want and let's actually adjust this our temp is organized as two By a two words Thing is that we are going to have to stash Y This should not be an RTS. This should be a jump to Block loop. Yes, that only actually worked at all Because our files each have one block so here we are going to want to true Calculate the bit in the bitmap Which this block corresponds to and Either set it or clear it So it's set file that does the work so st bitmap is the code that actually Stores the bit a routine to get the bit from the disk space allocation map Set BC to the block number on return D will contain the original bit position for this block number and HL the address so we're actually we need to Offset into the bitmap To find the appropriate bite each bite contains the bits for eight blocks so we're going to need to end up with a pointer to the bite and Which bit in that bite it is? in xA and the Okay, so this will call get bitmap location So the first thing we want to do is to get the actual bit position Which is very simply you just and the the low byte with seven and Stash We then are going to shift the The block number right by three bits But we for to do this we either want to have the value in xA or The value in 10 zero we want it in 10 zero because that's where shift R is going to expect it So we're going to want to Swap a lot of these So that's going to be 10 plus 2 to be 10 plus 3 So we know we're putting the block number in 10 plus 0 and we had an extra in An extra entry point to the shift our routine that does not set a and x Because we know that the the value is already in 10 zero where we expect it to be that's all we need to do is it Yes, it is So we get the bitmap location. We now want to set or Clear The appropriate bit. How is this supposed to work over here? Ah Right, it's expecting CK bitmap to return whether it's allocated or not So we're going to put this we can't put that in We can't return that in anything other than a because we need to push and pop Okay, so to put that in X load a with load a with one and We want to shift the The one left to get a mask This is going to be really painful. I think we need more primitives So get bitmap Status we call Get get bitmap location. We then want to shift our one So that he's in fact this code here to get a mask and We are going to put this in a helper. So that then produces a mask No, we don't want to do that We're actually want to use Shift R So you put that in Y. Yeah, no, we don't We want to shift this bit right. I was expecting to use These but these use temp So we're actually going to do going to have to write a right Time and once again these registers are wrong. I don't want to use Y for that. I want to use X for that because We need to put zero into Y So that we can read The actual bite out of the bitmap Then we shift the bite right and exit Okay, so To set a bit We get the location We stick that thing in X We now need to mask off the old value So this will compute a mask we then Invert it and it with the bitmap bite write it back This will This will clear the bit Does PLA set flags? Good. It does. It sets the Z flag. So we can say If it's not equal then set bit Otherwise we clear the bit. So in order to Set the bit we compute the mask Based on the value in X and then rather than ending it we Or it in are not inverted as well. Okay. Well, that's fairly nasty code. Oh Just looking at this that's really cunning That's really cunning So rather than just shifting Left and right here what it does is it rotates the bite So that all the bits are still present just with the one we want in the least significant bit Position Then it can operate on the bottom bit of that value then it shifts everything back again and writes Now we can do that in the 8080 Because it's got eight bit shifts eight bit rotates but the 6502 has nine bit shifts so actually doing an eight bit rotate is Harder than it looks So I found some code here for doing eight bit rotates Which looks clean enough that I think we are actually going to put that in so I'm going to replace these with Rotates to rotate right eight times So to rotate left this one is rotate right So LSR a is logic shift right this rotates Rotates nine bits where the right most bit goes into carry and The left bit ends up with zero as a zero so So if the carry is set we manually Set the top bit, which is kind of ugly but works So to rotate left then we Do a shift left Again a zero be a will be inserted on the right and the left will go into the carry and Then we can do a ADC zero, which will add the carry on to the value Thus putting the bit that was in the carry into the low byte so Get bitmap location this turns the bit position in a so this gives us the Rotated block status in order to set We stash the rotated block status the updated rotated block status We now want to rotate left and then we can just Plunk it back into the bitmap like this, okay So what we're going to do is Get bitmap status that gives us the rotated This of course will overwrite temp zero Which is our block number so that we cannot then call set bitmap status because this has gone Because this consumes both x because rotate our aid will reduce x to zero and The block status So I think set bitmap status is going to have to do both at once It also consumes why because we need that to index Yeah, this kind of bit fiddling is something that 6.02 is Not brilliant at simply because there are so few registers So let's push the bit position here transfer A to X So this gets us the rotated status in a Clear the bit we care about We now want to load this Things you've got this on the stack in the way. So pull the bit position Put that in X Ready to unrotate pull the new bit We now want to or it with why but we can't because there's the 6.02 doesn't have that instruction I don't really see the way you're supposed to do things here is by putting the value in a 0-page byte, but I'd rather not use more more zero page So there is something we can do and it's a bit foul, but it will work So here we wish to So this value here This value here should contain the The bit that we want to put in the In the bitmap We just have to get it from here to here and the easiest way to do that is With self modifying code So assuming that does what I want it to do. No, it doesn't Oh No, no, that's fine. This needs rewriting. Okay So this will stash the bit the bit value we want Directly into The operand field of this or instruction Which is fine and likewise We want to get the the bit position here So that we can rotate We can unrotate this like so Just wonder is there a way in which we can avoid This we can push it onto the stack But you can only pop into a and a is in use because it's got the actual like value So I think this is probably the cleanest we'll do the cleanest we can do Okay, there is another thing which is in fact I have gotten the The rotate the wrong way around bit Block zero Wants to go in the highest bit of the byte so This is going to return a bit position of zero We really want it to return seven so we kind of want to Do seven minus this value That would involve a reverse tracks and guess what another thing that six oh two is not very good at Ideally we want to be able to say this so seven minus a But we can't There is a way so we invert the value and Add the thing you want to subtract So our bit number here can be zero to seven Block zero Will end up in bit seven block seven will end up at bit zero and we also have this the wrong way around No, actually, this is right So we want to rotate right by the bit position. Yep. Okay, so Update bitmap for dear end. We have put our set free flag into X So here going to change this to update bitmap status We are actually going to Get the set free flag Update bitmap status. This should then update the bitmap So does it work? Well We can run it and It will do this Which is just what I was expecting We're actually going to need to step through this or preferably dump the bitmap once we're done or To be honest both Break 1903 and continue Run Okay, we're in our code How do we identify? This piece of code go ldy 16 cpy 32 should be here somewhere We don't actually have terribly much code these roles are Here so we are in read dear entry Here is our update bitmap for dear end code at 1 a 09 right? We're here. So the set free flag is in X. It is a one so we store Y is the Position into the FCB which is 16. Are we at 20? No, we're not Are we a big disk? No, we're not A Memory address for here is pointing at the current dear end which is 5db Here is our dear end For the not routine so we have one block which is a two and everything else is zero So we fetch the block. We are here Stash it the high byte is zero Increment y to the next block store the high byte Check for a zero which it won't be a is to we are now here We store y but we fetch the set free flag into a which is a one We go to update bitmap status We are here so we poke the We Poke the block status into This instruction we then try to get the bitmap location, which is here So 10 plus 2 this should be Well, this is block 2 So a is a is one that's just that's not right This is wrong This should be a zero Okay, one a three eight we want to restart from one a three eight. Okay, LDA six This gives us the low Block byte of two We've ended it with seven invert Reverse subtract Our value is five Which is Correct we then Push onto the stack. We now want to shift temp zero right by three We actually know this works. So and we can abbreviate this code a bit by using a Logic shift right for the first roll. I'll deal with that later. We're here Restore a which is still now five and we are here So we store the bit position We now want to do the rotating stuff. Yes, this has consumed the block number Intemp zero So this should return the byte in the bitmap which Block zero and one are used by the directory. So this should be C zero and It's not What is the value at six? Oh, oh, oh, oh We have not added the bitmap address so low byte of No, hang on bit maps a pointer low byte of bitmap C and C ADC 10 plus zero 10 plus zero Plus one Okay, we are here. It was the wrong button. Where are we? That's an LD a We're here. That's actually That's actually right get bitmap location good So we compute the location shift Right to get the bitmap pointer Now we are going to add on the bitmap address Which is that six six B? So if you look at that address there is our C zero So we're now here So we now load the byte there we get our C zero and a and we rotate it That's not very rotated That's because we haven't asked it How much to rotate by that's why so we need a TAX there For the first rotate bit pause here has been used for the second rotate So we're at get bitmap location No, we're not We're at update bitmap for deer ant Now we're at get bitmap location We are here store Get the value and That did not rotate that crashed Okay, so 3d reset New doesn't reset very reliably. Okay one B 3d. We are at rotate our 8 that needs to be a x No, it doesn't It does need to be one of these but I forgot to put a label in here. Here. We are and rotate our 8 X is 6 To rotate wasn't it five last time that's because we just inked it So we break out one before nine continue. We're now exiting from here. Here is our rotated value that is Zero one one zero Which is exactly what we want. So we're here We mask off the bit we care about this won't change because it's not set We are in the new bit See this as we've poked a one into the operand field here giving us a seven We now want to rotate left giving us E zero and Then we are going to store it back in the bitmap Done so that was the first file we go again. We should hit the second file Here we are in the rotate end of the rotate Here's the rotated value which you can see contains The bit that we set last time So this will set another bit making it an F So the first four blocks of the file system are in use and we stash it back and then we hold Good that works We are now correctly generated the bitmap So we have actually Now executed all the way to here. So now we need to talk about actually Now we need to talk about FCBs FCBs are how CPM Managers files POSIX and most operating systems that people are used to these days use file handles You open a file. It will allocate you a new file handle The data for which is stored in the kernel and you just get back an index to the file handle, which is yours CPM doesn't work like that instead CPM you go Makes you the user application Track a Structure, and this is actually the deer end which is slightly different but and The structure lives in application space and that contains all the information CPM needs to manage the file The advantage of this is it's much easier to do memory management because it's all statically allocated by the application and things like You don't need to close files if you don't have data that needs flushing you just discard the structure Because there is no cleanup that needs doing So an FCB Actually looks like a deer end the 32 bytes of directory information There are some of these fields are used for something else When they're in memory than when they are on disk and there's another three bytes stuck on the end Which is used for random access Actually, I know where to find this If we go to the BDoS call for Opening a file because that's what we're going to have to do next The FCB is 36 bytes structure Here it is So you can see this looks almost identical in fact the we have These four bytes on the end CR is the current record which Tells you which record you're looking at within an extent and These three fields here Their mark does reserved which is not terribly helpful, but they're used to track which extent you're in Oh, and DR here contains the drive number not the user number so Here in our warm start code What we want to do is to load the CCP to do this we need an FCB that we're using to Reference The CCP the command processor So this is going to be a structure We seem to have already decided that our code is mutable. So it's on drive a always four five six seven eight CCP dot sis These four fields need to be zero on entry we then have these fields are This is where the directory allocation lives this is not something the user should care about and The current record is zero and We don't actually need to allocate space for these three because these are only used by the random access functions So so we want to open the file So now we kind of want to write this and this is going to be one of our major entry points If this returns an error code Okay, so let's just put a halt here and A debug line, so now we should be able to run it There we go, we are trying to open the CCP so what is this actually going to do well The first thing we want to do because we're going to be accessing the CCP the NFC be we want a Variable for that so I need to figure out what the BDOS implementation here is Got calling this is the CCP. I think open it was the one Looking for the jump table. That's the That's the actual BDOS entry point which should be somewhere around Here it is Open fill is called so this is not actually complicated this Needs to make sure that the fcb is initialized it selects the disk described in the fcb and Then it jumps to the internal open routine. So we Clear the s2 byte s2 is 14 like so you can Either have a drive specifier in here in the first byte or zero This is really irritating Because this is a different drive numbering scheme than you then is used actually in the rest of CPM Because we have drive one being a rather than drive zero, but that's what it is. So so we want to load the Get the drive bite if it is If it is zero then we actually want to get the Drive here Select the drive This is more complicated than I thought it would be Okay, we're not Yes, we need to set the active drive and then select it and we only do that if It's a Different drive This is more complicated than it ought to be to be honest. I Think it's assuming that if the drive is zero then you get the currently selected one I'm not sure I like that terribly much. I think it should just be this So that if it's not Zero then We want to Decrement it if it's not zero then this means the user has specified a drive number. So we decrement Just thinking the least bad way to do this Want to decrement it and stash it an active drive? like that Okay, so we should now have arrived here with a selected drive and A valid FCB Okay, we're here. So in CPM 80 Open it here is the is the routine that does all the work and There's actually not very much of it because it all the work has mostly been done elsewhere So what it actually does is? It stands the entire directory Using find first here Looking for a dear end with the matching file name and a Extent counter of zero this means that it's the first the first Directory entry of the file Then it just copies the directory entry into the user FCB It makes a few a few adjustments and that's it. That's all that you need So we need find first Find first is a primitive that's used absolutely everywhere That was renames was changing attributes opening a file closing a file Find a empty space in the directory get the next extent of a file Random access stuff This is the actual directory scanning stuff that is exposed to the user and so on You tell it how many significant bytes of the FCB you want it to check for? So you can give it 11 for just the file name 14 for the Sorry hang on we start again from the beginning actually we you give it 12 if you want to scan for a File in a particular user Because this first byte here actually will actually get replaced with the user number you're looking for It's only a drive When it's in user space Or you can give it 15 to indicate that you want one specific extent Or you give it just one to indicate that you're looking for an empty space So what this actually does is it just? Resets the file position that we've done before we did that when logging in the drive Somewhere here and then it does lots of reading directory entries so So find next looks for the next file and it's going to be a loop where we look We look at each directory in turn if we reach the end stop In fact if we reach here Then we haven't found whatever we were asked to find and then there's all the Comparisons stuff while there's a lot of this and you can search for wildcards So we want to actually Want some variables for storage here? so we're going to have So this is the count of characters that we Want Actually, I'm just wondering do we need to When preparing the FCB do we also need to replace the Drive byte with the current user number. I think we do So this was open file Here s2 clears the Yeah, that's not complicated It's used to it uses a flag bit For things like you know is the file modified if so then when the FCB gets closed We need to flush the FCB out into the directory So here is open it so find first Must be Modifying the FCB if necessary character count File name to match is in params. I'm not quite sure what it's doing here What is params? DE parameters here on entry Okay, this is This is the pointer to the FCB then So that is fetching the FCB pointer that the user supplied and stashing it in save FCB clear initial file position home the drive and Then we start fetching directory entries so Read directory entry Check to see if we're at the end branch so here Here we actually say that we want to read we want to buy FCB Find first That does actually work up to this point. So we've read a directory entry We now need to match it to see whether It's something we care about So first thing is to check for a empty directory entry so here we jump to find next one if the DE is the pointer to the deer ant if it's e5 we go here Doesn't make a lot of sense. I would have thought that would be That should be a not Zed That is if we want to continue with the match only if it's not a DE5 So yeah, I don't know how that works. It could be a typo. Let's take a look at the other one This is the same file, but in 8080 machine code Yeah, these are actually the same comment. So that's not helping. Oh wait a minute. Wait a minute This isn't loading from current deer ant. This is loading from current FCB So as you see it's loading save FCB into HL Putting it in DE and then loading that it wants to know whether the user has asked to match for deleted entries Then it's going to be using This Yeah, if if the user has asked for deleted files it then will skip this check here Which seems to be checking for Whether we've reached the end of the directory or not It tracks how many files are in a directory So that it doesn't have to load the entire directory every time you want to do something Yeah, we haven't done that the the directory log-on stuff. It will scan the directory and Keep track of the last File Directory entry in the directory and it's stored in scratch one. We haven't done that at all. So We have to get here where we're actually comparing the file names So there's a bunch of setup Which is actually easier for us as we don't need to set registers because you've got everything we need in zero page So we load the number of significant bytes so we now want to get a file name from the Character from the FCB I'm sorry that should be a y and in fact That'll be one less So we're going to we actually start at the right hand end of the deer ant That's what the code here is doing loads the number of No, it doesn't know it doesn't it counts up? Okay So we are actually going to do that like this So we load a byte from the FCB If it's a question mark we We don't compare this byte Also, we This is clunky code. I have to say we also Don't care about byte 13 which is zero one two three four five six eight Nine ten eleven twelve thirteen. That's s one That's on on disk. That's the unused byte if it's the If we're on byte 12 the extent byte otherwise We compare the two characters by subtracting them if they are not the same then we Give up and we go right back to the top to find next so if the characters are the same then we go to Find next for which is here compare with Can we do cpy With an absolute address. Yes, we can if If we have not reached the end of the string go back to the comparison loop Otherwise so Looking at how this is done so what these will actually do is Return you a value zero one two or three Telling you which directory entry in the directory buffer Your matching dear end is We already have our directory Our current dear end pointing at the right place So all we need to do is just stop No more files wants to return an error very simple. Oh and we also want to reset the Directory position counter So there's one thing we haven't done, which is we need to compare Extents we're actually going to do this Okay, where's the extent comparison code? Here it's using a routine called Sam X which is used in one place so because a Directory entry can contain either one or two Extents we need to do some special stuff here this gives us the extent mask we now want to and Both the extent in the Directory entry and the extent in the FCB and check to see if they are the same Well anding the FCB extent is easy of course, we can't just compare it to the the one in The dear end let me just take a quick look here. What does this bit instruction do nothing useful? So I think we actually need Storage it's in order to do a comparison. We have to have one value in a and the other value in Memory somewhere, okay, so Pull the mask and With current here and come away Compare with the value that we stored earlier in the extent mask in the extent bite only the The bottom five bits are valid. So If the result is zero, they are the same Otherwise They are not the same and we go around again, okay so We are looking for four jsr's in a row That's our FCB Which is here. No, it's not it's here Here are four jsr's in a row. So this STA at one nine nine F is Find first so we put our rate point there Okay, we are comparing 15 bytes So store that home the drive Reset the directory position Now we're in the find next loop read a directory entry Check to see if there are if we've reached the end of the directory We haven't Did the user ask for deleted files? Well, that should be a cmp So that's not going to help the user did not ask to see deleted files In fact, let's just One nine three three is and that's the dear end for ccp.sys so We are now comparing the file names between the The value in the dear end and the value in the FCB So load a byte out of the FCB. That's the first byte which is the user I Still don't know how the FCB drive gets reset to be honest. Is it a wild card? It's not a wild card Is it by 13? It's not by 13. Is it by 12? It is not by 12 So we go up to the character comparison stuff So if I Look at four. That's oh five DB Here is the dear end we just read So we're going to be comparing zero and zero giving us zero Mask off the top bit. That's top bits of the file name are used to store file attributes They are the same so increment Have we reached the end of the string? No, we have not so we go around again So we should now be comparing a N for not and a C for ccp. So this should fail Check for a wild card saying characters blah blah blah That gives us f5 Mask That is not the same so we give up And go back up to find next So we read the next directory entry as the user want to see deleted files again Prepare file names again Yes, I feel reasonably confident that this is going to go around Until we hold So we hit the breakpoint here. We should have failed to find a file Because we don't have a ccp dot sis so if there is if the carry is set this means that We failed to find something we wish to exit That wasn't right That wasn't quite what I was expecting. I have to say so what I was hoping was that This would error out This would exit with carry set We would hit exit here And return and we would get this message rather than This message the question is of course why they I'm not sure that my debug string has worked Which would be annoying. I think this is failing somehow Let me take a moment and fix it Okay, I have it working Everything was fine. I just had incorrectly set the stack pointer and as a result my debug of routine here was Falling off the top of the stack and bad things were happening. Okay, so we actually want to Put a proper error routine in here to say that You know, it couldn't find the ccp, but we're not going to do that now Okay so We now want to Create our ccp So we create a dot s file for it. We add this to the make file Okay Actually we want to put this in the file system. Okay, so that has now created a Ccp dot sys that has gone into the cpm File system so we can now see that there are three directory entries Of which ccp dot sys is one block four There is in fact no ccp data So it's like Two ffs for the relocation table both of which are empty and no relocation header Which is quite wrong, so I'll just fix that Not here is our not routine And this is This is a com header So in fact that will do just fine Okay, that builds So now when we run our program we should get a different message Yes Good What that means is that we've iterated through the directory Until we have found a a dear end with a matching file name and We then have some stuff that we haven't done yet To actually Open the file when I say open the file. I mean copy the dear end into the The fcb so we found the first one and what is this doing? Right we want to fetch the extent byte out of the user fcb Because it doesn't necessarily match the one on disk so that is going to be extent byte is 15 14 12 Stash we are now going to copy Dear end to the fcb so 31 bytes There's a loop you know how loops work So this copies all the allocation data also I realize that our Extent comparison is I believe our extent comparisons incorrect I think we should be checking for extents that are less than the one the user asked for Because you're allowed to fetch extents that don't exist on disk yet anyway, we copy the dear end We want to set bit 7 of s2 to indicate that the Directory is unmodified s2 is 14 Get the extent byte back again. Is this correct then by within the user fcb Okay, this is the address of the fcb and because we have copied to The fcb is now the same as the dear end So this is to Is it cpm tracks the length of files in records not in bytes so that if the Directory dear end match if the directory extent matches the user extent Then we are looking at the last extent of the file Therefore we use the record count in the file because it's partially full If it is smaller Then we're in the middle of the file So we use a maximum record count size if it is larger then we are after the end of the file and We use zero because there are no records here if the carry is set. This means the user byte is bigger than the Dear end bite So the user extent is smaller. Therefore the record count is going to be 128 larger and you want to set the record count which is This one Current record Do we want to that current record within extents that the right one or do you want to set the other parameter? 15 right no, that's a different one. We want to set this one and CLC to indicate it works and we're done. Okay, let's try this Debugger so we are at the top of the file Go through the setup code. We're now in exit reset the disk Open the CCP Carry is not set So if we look So it thinks it's succeeded. So let's look at our CCP which is in one nine four our FCB which is in one nine four five oops five we see Yes, look there's an allocation We have successfully opened a file on our CPM file system So the next thing we're going to do is to actually read the CCP into memory Relocate it and run it But we're going to do that next time So until then