In recover, we've taken a memory card from a camera
and accidentally deleted all of the images.
Your task is going to be to write a program to recover those images,
generating new JPEG files for each.
How are you going to do that?
Well, first, you're going to want to open the memory
card file that we'll give to you.
Then, you're going to look through that memory card
file for the beginning of a JPEG file.
Once you find a JPEG file, you're going to open a new JPEG file
that you're going to start writing to.
And you're going to keep writing data in 512 byte chunks
until you find a new JPEG file, at which point you can close the old one
and start writing the new one.
And you're going to repeat this process--
looking for new JPEG files, writing new data to those JPEG files--
until you reach the end of the file.
How are you going to do all of that?
Well, let's start by talking about how you're
going to open the memory card file.
To do this, you can take advantage of the fopen function, which
will take as its first parameter the name of a file you want to open.
And the second parameter represents what mode
you want to open it in, where r stands for read mode, where you want
to read information from that file.
As you're reading that file, though, you're
going to want to be on the lookout for JPEGs.
How are you going to know that an image is a JPEG?
Well, every JPEG begins with a distinct header,
meaning the first byte of every JPEG file is 0xff in hexadecimal.
The second byte is always 0xd8.
The third byte is always 0xff.
And the fourth byte could vary a little bit.
But it's always 0xe0, or 0xe1, or 0xe2.
Anything that starts with 0xe and then something.
So it could be 0xef, for example.
These are all valid JPEG headers.
So if you notice this pattern of four bytes at the beginning of any 512 byte
block, you can pretty safely assume it's the beginning of a JPEG file.
So let's talk a little bit more detail about JPEGs.
Each JPEG is going to start with a distinct header, where
the first three bytes, as we've talked about, are 0xff, 0xd8, and 0xff.
And the last bite is 0xe0, or 0xe1, or 0xe2.
Anything up to 0xef.
So this is the header that you're going to be looking for.
And we'll tell you that each of the JPEGs
are stored back-to-back in the memory card.
After one JPEG ends, the next one begins, and so on and so forth,
up through the end of the file inside of these 512 byte blocks.
So what does this mean for how you can look through this memory card
to try to find JPEGs?
Well, if you imagine your memory card as a whole bunch of these 512 byte blocks,
then what you can do is start at the first block of the JPEG file,
and check to see if you found a JPEG header or not.
If you haven't found a JPEG header, you'll
keep moving on, looking one block at a time
until you find that sequence of four bytes that
indicates to you that this block is the beginning of a JPEG file.
Once you find that block, now you can open up a new JPEG file
that you're going to start writing to.
And you can start writing block after block after block
as you find more and more blocks of this JPEG file.
What you'll run into eventually, though, is
another block where the first four bytes also
look like the start of a JPEG file.
And when you detect that, you should realize that this is probably the start
of a new JPEG file, meaning you should close the old one--
you're done writing to it--
and start writing to a new JPEG file that has
all of these next sequences of blocks as the data within it.
So you continue writing again and again.
And you'll repeat this process, looking through this file,
until you find the start of another JPEG file.
Then, starting to write that new file.
And repeating that process again and again
and again until you reach the end of the memory card, at which point
you'll have found all of the JPEG files that are inside of this memory card.
How are you going to read data, though, from the memory card?
Well, to do that you can use the fread function, which takes four parameters.
The first parameter is called data.
And it is going to be a pointer to where you're
going to store the data that you're reading,
likely some buffer of some kind that might be an array, for example.
Next is size, which is the number of bytes of each element you're
going to try to read from the file.
Next up is number, which is the number of those elements
that you want to try to read all at once.
And then, finally, is inptr, which is the file that you're
going to actually read that data from.
And recall that what you want to do is read from the memory card
file in 512 byte chunks.
Once you've read one of those 512 byte chunks, though,
how are you going to know if that 512 byte chunk is actually
the start of a JPEG file or not?
Well, recall that the JPEG file has a distinct header.
So if you've read this 512 byte block into some sort of buffer,
which might be an array of bytes, then what you can do
is check to see if buffer square bracket 0--
in other words, the first byte of the buffer--
is 0xff, the first byte of a JPEG file.
And likewise, you can do the same thing.
Checking if the second byte in the buffer is 0xd8
and the third byte in the buffer is 0xff.
The fourth byte is where things get a little bit tricky.
Recall that there are 16 different values that buffer square bracket
3 could take on.
It could be 0xe0, 0xe1, so on and so forth.
And so what you might imagine doing as a first pass is a Boolean condition where
you ask yourself, if buffer 3 is 0xe0 or buffer 3 is 0xe1, or 0xe2, or 0xe3,
your 0xe4, so on and so forth.
But doing this 16 times is going to get very, very tedious.
So to simplify, we can use a trick known as bitwise arithmetic
where I can take the bitwise and of buffer 3 and 0xf0.
What that's going to do is it's going to say, just look
at the first four bits of this 8-bit byte,
and set the remaining four bits to 0.
What that means is that 0xe0, 0xe1, 0xe2, so on and so forth all
become 0xe0 because we've cleared out those last four bits.
Then, after we've done this bitwise and, we can just compare the result to 0xe0
to determine whether or not this byte is a byte
that might appear as the fourth byte inside of a JPEG.
If all of these conditions are true, it's pretty likely
that the 512 byte block that you found represents the beginning of a new JPEG
file.
So when you found the beginning of a new JPEG file, what you'll need to do
is create a new file where you're going to write this data to.
How do you make a new JPEG file?
Well, each file should have a very particular file name as digit digit
digit dot JPEG starting with 000 dot JPEG in the order in which you find it.
So you'll likely want to keep track of how many JPEGs you found so far
so that you can write the correct file names in the correct order.
How do you create a string of the format digit digit digit dot JPEG?
Well, to do this, you can take advantage of a function called sprintf, where
you're printing not to the terminal, but to a string where
the first parameter is the name of the string you want to write to.
In this case, file name.
The second parameter is the format string.
%03i just means print an integer with three digits to represent it,
even if a number like 0, or 1, or 2 doesn't really require 3 digits
to print out that integer.
And the final parameter is the number that you want to substitute.
In this case, 2, where the result would be that the file name string would now
contain 002 dot JPEG instead.
Make sure that file name, though, has enough memory
or has enough characters to fully represent this entire file name.
After you've created the file name, you can open a new file with that file name
by calling fopen again.
This time, providing as the first argument the name of the file you
want to open.
And now, the second argument to fopen instead of r for
read it is going to be w for writing so that you
can begin to write to this new file all of the data
that you're going to find from the memory card.
How do you write data to a file?
To do that, you can use the inverse of fread, which is fwrite,
which is going to write data to a file.
Fwrite, just like fread, also takes four parameters.
The first parameter, called data, is going
to be a pointer to all of the bytes that you want to write to the file.
Next, you need to tell fwrite how big the elements that you're going to write
are.
So the second parameter, size, represents
the number of bytes in each element that you're going to write to the file.
Next is number.
The number of elements that you're going to write to the file.
And finally, outptr is the file pointer that you actually
want to write the data to, likely the JPEG
that you've just opened for the purpose of writing to it.
And you're going to continue doing this--
writing data to JPEG after JPEG--
until you reach the end of the file.
But how do you detect when the end of the file is?
Well, to do that let's take another look at fread,
which is the function that we're using in order
to read data from the memory card that's given
to you at the beginning of the program.
What does fread return?
Well, it turns out that fread returns the number of items of size size
that were read.
We currently try to read number elements of size size.
And so most likely, if fread is able to read all of that data,
it's going to return back to you number.
Say that I'm trying to read 255 elements, for example.
Then fread is going to return to me 255.
Of course, once I reach the end of the file,
it might be the case that I don't have 255 additional bytes to read,
and I have fewer than that.
In that case, fread is going to return to me
some number that is less than 255.
So you might want to think about what condition you
could write to determine whether fread has gotten to the end of the file
or not.
Let's put all of this together now and start to think about some pseudocode
that you could use to implement this recover program.
The first thing you'll want to do is open the memory card.
Then after that, you're going to want to repeat some process until you
reach the end of the card.
You'll want to read 512 bytes, likely using
fread into some sort of some buffer-- some space in your computer's memory
where you have 512 bytes worth of storage
that you can read that data into.
Then, take a look at the 512 bytes that you've read.
If it's the start of a new JPEG, which you
can detect by looking at those first four bytes
and determining whether or not it's a JPEG header or not,
then you know you've found a new JPEG.
So if this is the first JPEG, you should start writing 000 dot JPEG and start
writing your very first file.
Of course, if you've already found a JPEG,
then you'll need to make sure to close the file you've just
been writing to so that you can open up this new file
that you're going to continue writing to.
So that's what to do if the 512 bytes you read is the start of a new JPEG.
But if it's not the start of the new JPEG, what should you do then instead?
Well, if you've already found a JPEG and you've already been writing to it,
then you should keep writing to it.
This is the next 512 byte block of this current JPEG
that you've been writing to.
And you might repeat this process multiple times
because every JPEG might take up multiple blocks
of memory inside of the memory card that we've given to you.
So you'll repeat this process--
reading 512 bytes and then checking.
If it's the start of a new JPEG, you might need to do something.
If it's not the start of a new JPEG, you might need to do something else.
After you've reached the end of the memory card,
then you should close any remaining files.
And if all goes well, you should be done.
And you should see that you've now generated
a number of JPEG files, each of which contains image data that you can then
open up and view visually.
My name is Brian, and this was recover.