
Who robbed Thibouvre ? 1.0.0
file manipulation

If there is a conflict between the subject and the skeleton or moulinette, the subject is always right.
However, please report any inconsistencies to your assistant.

Submissions
intranet: https://intra.forge.epita.fr/epita-prepa-computer-science/prog-103-p-05-2030/root/
opening: 2026-02-16 10:42
closing: 2026-02-23 10:42
git tags:
submit-* (limit: 3 per week)
archi-* (limit: 2 per hour)

Repository structure
At the end, your git repo must follow this architecture:
epita-prepa-computer-science-prog-103-p-05-2030-firstname.lastname
├── Who_robbed_Thibouvre
│   ├── fundamentals
│   │   ├── basic_read
│   │   │   └── basic_read.c
│   │   ├── basic_write
│   │   │   └── basic_write.c
│   │   ├── common_streams
│   │   │   └── common_streams.c
│   │   ├── count
│   │   │   └── count.c
│   │   ├── filter
│   │   │   └── filter.c
│   │   ├── hidden_message
│   │   │   └── hidden_message.c
│   │   ├── my_cp
│   │   │   └── my_cp.c
│   │   ├── my_grep
│   │   │   └── my_grep.c
│   │   ├── parser
│   │   │   └── parser.c
│   │   ├── print_lines
│   │   │   └── print_lines.c
│   │   └── write_format
│   │       └── write_format.c
│   └── proficiencies
│       ├── decoder
│       │   └── decoder.c
│       ├── get_data
│       │   └── get_data.c
│       ├── save_data
│       │   └── save_data.c
│       └── scavenger_hunt
│           └── scavenger_hunt.c
├── .gitignore
└── README
Copy

Do not forget to check the following requirements before submitting your work:
You shall obviously replace firstname.lastname with your login.
The .gitignore file is mandatory.
Remove all personal tests from your code, except those from the Tests folder.
The given prototypes must be strictly respected.
The code MUST compile! Otherwise you will not receive a grade.

.gitignore example
Here is an example of a .gitignore file:
*.a
*.lib
*.o
*.obj
*.out

.idea/
*~
*.DotSettings.user
Copy

This needs to be setup before the first submission!

Introduction

Hello, welcome to this new practical!
You are going to learn new skills about File management: How to open, to read, to write, to close files and many other related skills!

We are also asking for your help because a terrible event has just occurred... Someone committed a huge heist in Thibouvre, the famous museum of the Island!
We will need your help to find who did this terrible crime. We will investigate, and look for clues hidden in files you will find during your adventure...

Information
This practical will be presented as a CTF (Capture The Flag).
It means that some flags are hidden in the practical.
They will be required during your investigation.
(do not worry, we will not ask you the flags. It is just a way to get the practical less boring :) )

When an exercise contains a flag, it will be clearly marked with a comment in the example section.

Information
As this practical will use a lot of files, do not forget to extract the given files!

Information
During this practical, you may include any header files (.h) you wish.

We strongly encourage you to take a look at string.h and stdio.h with man.
They contain many useful functions that will save you from having to rewrite them yourself.

However, to be honest, we are afraid you have a few things to learn first.
We will progressively teach you the basics of file manipulations, and then use your new skills to progress in your quest.


fundamentals/basic_read/basic_read.c

First, you will have to achieve simple exercises to understand how to use the basic functions in the <stdio.h> library.
You will mainly use four of them: fopen, fread, fwrite, and fclose (the f prefix simply stands for file).
As it is your first exercise, we will slowely explain the procedure of file management, step by step, to understand its behavior.

Information
As these functions are part of the stdio library, do not forget to include it.

First, we just want that you open and close the file whose name is given as parameter.
Be careful! Opening a file can fail! (if the given file does not exist, or if its access is forbidden for example)
If such a case occurs, you must return 1. Otherwise, simply return 0.

Hint(s)
Take a look at the functions given above, as well as their corresponding man pages.
This is where you will find how to check if the opening procedure failed or not.
Moreover, you will want to fill the "mode" parameter. We will learn it later, just set it to the string "r" for now.

Warning(s)
Never forget to close the file!
Indeed, opening a file kind of locks a file slot, which occupies some space in your program.
Get into the habit of closing it, that will be important for your bigger projects.

Prototype(s)
int open_close(const char *filename);
Copy

Code example(s)
int main() 
{
    int a = open_close("does_exist.txt");
    int b = open_close("does_not_exist.txt");
    printf("%d %d\n", a, b); // 0 1

    return 0;
}
Copy

Okay, now it could be great to read what is inside this file.
Do the same code that in the previous function, but now you need to read 
N
N bytes inside. (which is the second parameter of the next function)
Next, print these characters.
As previously, return 1 if an error occurred or 0 if the function successfully executed.

The procedure is as follow:
Open the file. If there is an error, return 1.
Initialize a char array of size n + 1, using malloc. The + 1 is mandatory if we need to add a null character after the resulting string.
Read n characters, from the file, to the array, and store how many characters were read, into a variable m. It is required as it can be less than n if we reach the end of the file.
If an error occurred in the previous step, free the array, close the file and return 1.
Add the null character in the array at index m.
Now that the null character has been added, you have to print the array's content. You also have to print a line break at the end.
Do not forget to free the array and close the file!
Apparently no error occurred, so we can return 0.

Hint(s)
The third point looks harder than the others, but it becomes easy once you read the fread(3) man page!

Prototype(s)
int read_n_bytes(const char *filename, size_t n);
Copy

Code example(s)
/*
$ cat does_exist.txt
Hey, as you can see, this file is not empty and therefore you can open it, read it, and close it!
*/

int main()
{
    int a = read_n_bytes("does_exist.txt", 32);
    printf(" | %d\n", a);
    /* Expected:
    Hey, as you can see, this file i
     | 0
    */

    int b = read_n_bytes("does_not_exist.txt", 5);
    printf(" | %d\n", b);
    /* Expected:
     | 1
    */

    return 0;
}
Copy

Well done, you can read! But reading the entire file is better, right?
That is the goal of the next function.

Information
The new code will be pretty similar, but you should repeat the steps 3 to 6, until no character remains to be read.

Moreover, if you were able to read the file, you must add a new line at the end of your output.

Prototype(s)
int read_bytes(const char *filename);
Copy

Code example(s)
/*
$ cat does_exist
Hey, as you can see, this file is not empty and therefore you can open it, read it, and close it!
*/

int main()
{
    int a = read_bytes("does_exist.txt");
    printf("--> Return code: %d\n", a);
    /* Expected:
    Hey, as you can see, this file is not empty and therefore you can open it, read it, and close it!
    --> Return code: 0
    */

    int b = read_bytes("does_not_exist.txt");
    printf("--> Return code: %d\n", b);
    /* Expected: 
    --> Return code: 1
    */

    return 0;
}
Copy


fundamentals/basic_write/basic_write.c

Great job so far !
Now that you know how to read, you may want to know how to write.
The next exercise will teach you how to write a string into a file.

In your next function, use a loop to write the entire given string to the file given by its name.
If the file exists, replace its content. Otherwise, you will also have to create it.

Do not forget to open and close your file !
We swear that we'll not remind you this anymore, BUT this time, there is something different.
Previously, you used the opening mode "r", which just allows you to open the file for reading purpose. Now, you want to write on it, and even create the file if it does not exist.
Take a look at the fopen(3) man page, and use the appropriate mode.

Hint(s)
You can use the fputc function to put a char into a file.

Danger(s)
The writing opening mode erase the content of a file when you try to open it.
To prevent this kind of situation, be sure to specify to fopen the right path.
We also suggest you to git push a lot, even without any tag, just in case.

Information
Please note that, every time you see a prototype returning an integer, it means that you have to do as the usual.
So basically, except if we specify something else, return 1 directly after an execution error, or 0 otherwise.

Prototype(s)
int write_one_by_one(char *filename, const char *text);
Copy

Code example(s)
int main()
{
    // does_exist.txt contains: "something which will be replaced"
    int a = write_one_by_one("does_exist.txt", "Hello World!");
    // does_exist.txt contains: "Hello World!"

    // does_not_exist.txt... does not exist.
    int b = write_one_by_one("does_not_exist.txt", "Now I exist");
    // does_not_exist.txt... now exists and contains: "Now I exist"

    printf("%d %d\n", a, b); // 0 0
    // The opening should succeed as it creates the file if needed
    // However it can fail if the file's access is forbidden

    return 0;
}
Copy

The function that you just implemented seems "simple", as it is "just" a loop, but in fact it is a horrible way to proceed.
Indeed, at each fputc call, you summon a system call, which is highly expensive for your poor CPU.
You will need to use the fprintf function instead. It can write the entire string in only one syscall!

Write the same function, but this time usage of any loop is forbidden.

Hint(s)
Take a look at the fprintf(3) man page.

Prototype(s)
int write_all_in_one(char *filename, const char *text);
Copy

Code example(s)
int main()
{
    // does_exist.txt contains: "Hello World!"
    int a = write_all_in_one("does_exist.txt", "What a good day!");
    // does_exist.txt contains: "What a good day!"

    // still_not_yet_exist.txt... does not exist.
    int b = write_all_in_one("still_not_yet_exist.txt", "Now I exist");
    // still_not_yet_exist.txt... now exists and contains: "Now I exist"

    printf("%d %d\n", a, b); // 0 0
    // The opening should succeed as it creates the file if needed
    // However it can fail if the file's access is forbidden

    return 0;
}
Copy


fundamentals/print_lines/print_lines.c

Let's put in practice what you have just learned.

In the next exercise, read a file and print it with the format: > Line content for each line.
You also must print a break line at the end of your output. Do nothing if you cannot open the file.

Information
You should take a look at the getline function.

Hint(s)
It may be a bit tricky to understand the behavior of the getline function.
To help you, here is a summary of each parameter:
lineptr is a result pointer.
When getline finds a \n, it will set the string that follows - which is the delimited field - into a string pointer (char **) given as parameter.
This function should thus be called several times, until the entire stream is read.
Be careful! This resulting field will be malloc-ed, and realloc-ed each time you call the function. Thus, you need to free it before exiting your program.
n is another result pointer.
If lineptr contains the resulting string, well this parameter contains the resulting length of the string.
As well as lineptr, you need to pass a reference as parameter (int *).
stream is the file stream.
It is simply the structure returned by fopen.

Prototype(s)
int print_lines(const char *filename);
Copy

Code example(s)
int main()
{
    print_lines("example.txt");
    /* Expected:
    > First line
    > Second line
    > Third line
    > and so on
    >
    > ^^^ Even empty lines ^^^
    */

    return 0;
}
Copy


fundamentals/my_cp/my_cp.c

You are now able to build your own shell commands !

Write a function which copies a file from src to another one at dest.
If the destination file does not exist, create it.
If the destination file already exists, replace its content.
Check the right opening mode to use.

Danger(s)
Same warning as earlier, be careful to not erase unwanted file contents...

Prototype(s)
int my_cp(const char *src, const char *dest);
Copy

Code example(s)
int main()
{
    /*
    example.txt <-- Create a copy of me!
    trashy.txt <-- rgqUvcNRfEucVnUIVpqyNnqKrEvXEchlYmqKwyDHrHqk#eO#BCh@uAKFaXrS@jtdnCvRPqwx#SgSiV_avkoQwJYl#Mv_@fJ#h#@FnLqmDOqwxsbdLv@WmhAc#LCxc@Fc
    */

    printf("%d\n", my_cp("example.txt", "trashy.txt"));   // 0
    printf("%d\n", my_cp("new_file.txt", "example.txt")); // 1
    printf("%d\n", my_cp("example.txt", "new_file.txt")); // 0

    /*
    example.txt <-- Create a copy of me!
    trashy.txt <-- Create a copy of me!
    new_file.txt <-- Create a copy of me!
    */

    return 0;
}
Copy


fundamentals/count/count.c

Count the number of characters, lines, and words in the given file.

If you can open the file, print an output with this following format:

> characters: [NUMBER OF CHARACTERS]
> words: [NUMBER OF WORDS]
> lines: [NUMBER OF LINES]

with a final new line.

A '\n' counts as a character.

We consider a word as a sequence of any characters followed by ' ', '\t', '\n', or the end of the file.

We consider a line as a sequence of any characters followed by '\n'.
Be careful, Vim adds automatically a new line at the end of the file.
Therefore, your function should consider it and display how many lines are visible on Vim.

Prototype(s)
int count(const char *filename);
Copy

Code example(s)
int main()
{
    return count("example.txt"); // <-- return 0, because example.txt exists
    /* Expected:
    > characters: 2030
    > words: 103
    > lines: 5
    */
}
Copy


fundamentals/write_format/write_format.c

Lore
Okay, now you should be able to start investigating about the robbery...
For this, you will need to find the password which will give you access to the security computer of the museum.
You know that the number 
7940247895376159821
7940247895376159821 can be converted into a string password using the function below...

Supposing that you want to write the result of an operation into a file.
You can use fprintf to compute the result into a string automatically!

The expected behavior will be similar to the printf one.
Indeed, you want to take as input a format string, followed by the variables that you want to print.
So, the next function will take as parameter some data, its type of data, and the name of the output file.

Prototype(s)
int write_format(void *data, enum data_type type, char *filename);
Copy

Information
As you can see, the first parameter has a void * type.
It means that it points to an unknown type, and is called genericity.
If you want to convert it to a specific type, you need to cast it.
For example, if you have a generic pointer void *var; and you write int *var_as_int = var;, then var_as_int will be an integer pointer pointing to the same address as var.

We give you the type parameter as an enum. It will tell you what is the type of the data.
Your function needs to print the data, with the format given as example, to the filename file.
If the file does not exist, create it.
If it does not, append the text at the end of the file. Be careful, you need to choose a specific opening mode for this.
In both case, add a final line break.

Information
The given enum is defined in the given write_format.c file. We also give it below:

Code example(s)
enum data_type {
    STRING,
    INTEGER,
    POINTER
};
Copy

Code example(s)
int main()
{
    char *str = "Write me!";
    int integer = 42;
    int *ptr = &integer;
    /*
    As you can see, we pass an integer pointer to the function.
    This is because genericity only applies to pointers, so you cannot directly pass an integer as a parameter.
    It also means that you will have to dereference it in order to retrieve its value.
    */

    write_format(str, STRING, "output.txt");
    write_format(ptr, INTEGER, "output.txt");
    write_format(ptr, POINTER, "output.txt");
    /** output.txt should contain:
    String: Write me!
    Integer: 42
    Pointer: 0x424242424242 // Unfortunately, it may not be the result you will get, but something similar
    */

    /* FLAG
    Complete the function in order to print the password into output.txt.
    */
    size_t password[2] = { 7940247895376159821, 0 };
    write_format(password, STRING, "output.txt");

    return 0;
}
Copy


fundamentals/filter/filter.c

Lore
Good, you just entered the security system of the museum!
The first thing you would like to do is to filter the visitors with a suspect criteria.
For example, we can list the credit card they used, and keep only those which are known as "suspect".
They could be concerned by previous fraudulent usage, like scams or things like this.
The next algorithm will allow you to filter them accordingly.

In the next exercise, you will use your new skills into a cryptographic interest.
Simply follow the instruction of the algorithm given below.

The goal is to read a series of lines filled with numbers. You want to check the validity of each serie.
This validity is given by the following algorithm:
You will read every digit of the line from left to right.
While reading, you will compute a sum acc.
If the digit index is odd, add it to the sum.
Otherwise, compute 
2
∗
digit’s value
2∗digit’s value.
If it is above 
9
9, sum the two resulting digits, and add it to acc.
Otherise, simply add this value to acc.
If the resulting acc is a multiple of 
10
10, print the line.
In any case, repeat the process until the entire file is read.

In fact, this algorithm is almost the same as the Luhn one, except that we are currently reading from left to right.

Hint(s)
Example:
Take the number: 1234567891011121
Focus on the even-indexed digits: 1.3.5.7.9.0.1.2.
and double them: 2.6.10.14.18.0.2.4.

As you can see, 
10
10, 
14
14, and 
18
18 are above 
9
9, so we sum their digits: 2.6.1.5.9.0.2.4.

Sum the digits of the final number: 
∑
∑2264165891012141
=
53
=53, it does not end with a 
0
0 so we do not have to print it.


Prototype(s)
int filter(char* filename);
Copy

Code example(s)
int main()
{
    /* example.txt
    1234567891011121
    1742909510526
    */

    int a = filter("example.txt");
    printf("%d\n", a);                 
    /* Expected: 
    1742909510526
    0
    */

    /* FLAG
    Apply this function to the flag.txt file, it will give you the suspect cards.
    You should obtain exactly 5 results.
    */
    filter("flag.txt");
    
    return 0;
}
Copy


fundamentals/my_grep/my_grep.c

Lore
Great, we have now a list of credit cards which could correspond to the thief one.
To know exactly which one corresponds, we need to look at another filter.
When someone purchases a ticket, he can choose how many entries are booked for the museum.
We think that the thief probably did some spotting work, which required several entries to be done properly before the big day.

The flag file will thus be a CSV file: it contains a table, but you can read it easily like a basic text file.
It will contain the list of visitors, as well as other useful information about each of them, including the credit card number and their amount of entries.
You should use a way to keep only the lines corresponding to the suspect credit cards found above.
Then, only note the ones which bought several entries.

You will implement the grep shell command.
Again, do not hesitate to check the corresponding manual page in any doubt.

grep is a very useful tool which allows you to fetch lines from a file, in which a given pattern is included inside.
We will give you a filename to read and expr, an expression that you will have to find as a substring in each line of the file.
This time, the return code will be different. Your function must return 0 if no lines contain the given pattern, 1 if there is at least one, or 2 if the file does not exist or if its access is forbidden.
Moreover, your function must print every matching lines found.

Hint(s)
You should take a look at the strstr function.

Prototype(s)
int my_grep(char *filename, char *expr);
Copy

Code example(s)
int main()
{
    printf("\n%d\n", my_grep("example.txt", "word"));
    /* Expected:
    This line contains words.
    And "words" contains "word".
    word
    1
    */

    putchar('\n');

    printf("%d\n", my_grep("example.txt", "eqlijgber")); // Returns 0
    printf("%d\n", my_grep("empty_file.txt", "word")); // Returns 0
    printf("%d\n", my_grep("inexisting_file.txt", "word")); // Returns 2

    putchar('\n');

    /* FLAG
    Try "grepping" the flag.csv file, by the credit card numbers you found in
    the previous flag.
    Then, only keep those which booked several entries.
    */
    my_grep("flag.csv", "Put here the credit card numbers");
    my_grep("flag.csv", "Put here the credit card numbers");
    my_grep("flag.csv", "Put here the credit card numbers");
    my_grep("flag.csv", "Put here the credit card numbers");
    my_grep("flag.csv", "Put here the credit card numbers");

    return 0;
}
Copy


fundamentals/common_streams/common_streams.c

Let's mark a little break in the story! You need to learn something new.

It is useful to know that some FILE * are already opened by default.
Indeed, when you do a printf("...") call, it is in fact equivalent to: fprintf(stdout, "...").
The next exercise will cover this notion.

There exists three default file streams: stdout, stderr and stdin.
stdout is the default output location of your program. This is where you print all your testing messages with printf for example.
stderr is another output location, but used for error messages. This is where your SEGV errors go for example.
Even if it is also printed in your terminal, it is still two different streams.
And finally, stdin is where the user can input something in your program.
As you can guess, you can only "write" in the first two, while the last one is only for "reading" purposes.

Information
As these streams are managed automatically, you must neither open nor close them.

Your next function will read lines coming from stdin, test if it matches a valid pattern, and:
If it is valid, print: "Accepted: %s\n" in the stdout stream,
If it is not, print: "Error: %s\n" on the stderr, where %s is the line which failed the verification.

The valid pattern follows the following regex: [a-zA-Z0-9]+\.[a-zA-Z0-9]+.
If you do not know what the regex patterns stand for, well this one just mean that the line must contain only letters, digits, and exactly one dot.
The dot must not be at the beginning nor at the end of the line.
It is almost the same that the EPITA's people logins format!

Moreover, you have no prototype in this exercise: write your program directly in the main() function.
When you will start the program, it should keep running until you type the CTRL-D shortcut.
It will send an end of file to the stdin, and will be thus the stopping signal of your program.
Before this, you can interact with the program by typing in it, and see the result when you press ENTER.

Warning(s)
You are not allowed to use any regex matching function.
This pattern is simple enough to be implemented yourself.

Hint(s)
The method to achieve this is to use a while loop. You want to keep getting lines until you meet the input's end of file.
You have already learned which function you should use for this, but you can still check its manual page.

Code example(s)
$ ./common_streams # Write this and the following lines in the terminal.
tom.nook
Accepted: tom.nook
.tomnook
Error: .tomnook
tomnook.
Error: tomnook.
tom.no.ok
Error: tom.no.ok
tomnook
Error: tomnook

Error: 
abc0.def1
Accepted: abc0.def1
<CTRL-D PRESSED, END OF FILE SENT>

$ # You should be back with your usual prompt
Copy


fundamentals/hidden_message/hidden_message.c

Lore
As you can see, only one line gave you a suspect credit card number with several entries...
However, the referenced name looks to be a fake pseudonym!
It does not allow us to directly find how is the author, but we can find some information about him on the Dark web!
We have just found a weird message he posted some years ago... It could give us relevant information if you try to decrypt it!

Let's find some hidden messages inside bigger text.

In your next function, you will have to pick some specific characters while reading a file.
The characters that you will want to fetch are located at the beginning of the file, and just after some delimiter characters.
It means that you have to find every character matching with the one given as parameter, print it, and repeat the process until the file is completely read.
Check the examples below if you have some doubts about what we are asking to you.

Information
You can use putchar instead of printf if you want to print only one character.
A useful function to get the delimiter is the getdelim function. It allows you to split a file stream into several strings.
Again, you should take a look at the man pages, even if its behavior is similar to getline.

In case that the final character is a delimiter, ignore it.

Prototype(s)
int hidden_message(const char *filename, const char delim);
Copy

Code example(s)
int main()
{
    hidden_message("example.txt", ':'); // Must return 0.
    // Must print nook

    putchar('\n');

    hidden_message("example.txt", ';'); // Must return 0.
    // Must print: n
    // Because no delimiter was found, so only the first character is printed

    putchar('\n');

    hidden_message("does_not_exist.txt", ':'); // Must return 1.
    // Must not print anything

    /* FLAG
    Decode the given message to understand what it means
    */
    hidden_message("flag.txt", '~');

    return 0;
}
Copy


fundamentals/parser/parser.c

Lore
The hidden message seems to be some coordinates...
It may be the place where he hides... which makes sense as publishing it on Internet allow his collaborators to find him at this spot!
Therefore, the investigation staff quicky prepared the stuff and went here ready to catch him... but it looks like he ran away just before we arrived...
To follow him, we would like to know which exit was the most relevant for him. It should be a quiet path that nobody takes.
You imagine a list of possible exit paths, and you assign a "discretion score" to it. Now, let's take a look at which one is the most plausible.

You want now to read a file and parse it.
It means that you want to organize your file so that revelant information appears.

For example, imagine a file formatted like this:

Code example(s)
Alice:42
Bob:15
SomethingAsText:123
Copy

As you can see, every line contains a pair of a string and an associated number.
Your next function will read each line of this kind of file, in order to retrieve some information.
What is the string associated to the biggest value?
What is the biggest value?
What is the mean of every values together?

If the file described as parameter does not exist or is forbidden, return 1.
If the file given as parameter exists but does not suit the format because:
it does not suit a valid format;
or it is an empty file;
then return 2.
Otherwise, print these expected values, with the same order, with one line by value, and return 0.

Information
If the average is a non-integer result, take its entire part.
If several maximum values exist, take the first one.

Hint(s)
Take a look at the atoi(3) man page.

Prototype(s)
int parser(char *filename);
Copy

Code example(s)
int main()
{
    int a = parser("example.txt");
    /* Expected:
    SomethingAsText
    123
    60
    */

    //
    int b = parser("empty.txt");
    int c = parser("wrong_delimiter.txt");
    int d = parser("wrong_number.txt");
    int e = parser("does_not_exist.txt");
    // Expected is nothing.

    printf("%d %d %d %d %d\n", a, b, c, d, e);
    // Expected: 0 2 2 2 1

    /* FLAG
    Let's read a file which contains every possible exit paths, assiociated with
    its their discretion score. You want to get the most quiet among them.
    */
    parser("flag.txt");

    return 0;
}
Copy


Wow you practiced a lot so far!
Now let's introduce new advanced concepts, which may look a bit harder


proficiencies/get_data/get_data.c

Lore
Great, it is pretty sure that he'd take this way.
Thus, you begin to walk towards this direction, until you get blocked by a wire mesh.
While you were asking yourself how to cross it, you can see a little bit of red in the obstacle...
It seems like the thief cut himself trying to go this way, and that he let a bit of blood behind him.
Your new task is so to analyze the DNA contained inside.

Sometimes, you might want to manipulate some files, but which do not contain human-friendly text.
For example, if you cat an executable file that you generated during your practicals, you will see that we cannot understand its content directly.
This next exercise will teach you how to read this kind of data.

The next function must read some bytes in a file and store it in a buffer that you will allocate.
The filename parameter locates the file that you will have to read.
The block_size parameter gives you the length of blocks that you will have to read.
The length parameter indicates how many blocks you need to read.

You must return an allocated array of size 
l
e
n
g
t
h
∗
b
l
o
c
k
S
i
z
e
length∗blockSize containing the length first blocks of your file.
Reminder: malloc is defined in <stdlib.h>.
Do not worry about freeing it, it will be done by the moulinette after the function call.

If the file does not exist or if the file is too short, return NULL to indicate an error.

Information
It is almost the same procedure as reading classic characters, except that the size of the read data is not always 1 byte.
Consequently, you need to change a parameter to your reading function.

Prototype(s)
void *get_data(char *filename, size_t block_size, size_t length);
Copy

Information
As you can see, the the function returns a void * array.
This is because the type of the returned data is not defined, and depends on its size.

Code example(s)
int main()
{
    void *a = get_data("example.txt", 1, 7);
    printf("%.7s\n", (char *)a); // Prints: Example
    free(a);

    return 0;
}
Copy


proficiencies/save_data/save_data.c

Let's do the inverse operation for fun.

Now, instead of reading data, you need to write it.
The next function takes the same parameters as previously, but we add a void * pointer which is data to write in the filename file.
If the file does not exist, create it.

Prototype(s)
int save_data(char *filename, size_t block_size, size_t length, void *data);
Copy

Code example(s)
int main()
{ 
    char *data = "This is readable text.";

    save_data("new_file.txt", 1, 22, data);
    printf("A new file has been created.\n");

    /* FLAG
    Analyze this serie of blood drops and convert it to DNA.
    */
    size_t t[7] = {
      7959390400869134169,
      8030516705471242340,
      4705773566797636194,
      3765928769057995296,
      4708850085609554226, 
      3338648646059311435, 
      5497845422045015598,
    };
    save_data("flag.txt", 1, 50, t);

    return 0;
}
Copy


proficiencies/scavenger_hunt/scavenger_hunt.c

Lore
You collected a lot of different clues so far, it is the moment to put them together to reveal the truth!
However, you awkwardly dropped the folder containg all the proofs, you will need to order it again...

The next file is a mix of a lot of different data... Relevant information are now completely shuffled!
But there's still a way to retrieve it, because the file follow two interesting rules:
The beginning of the file contains a clue structure.
If you look at the given scavenger_hunt.h file, you will see that it contains a letter and a next_offset integer.
The associated character is the first one of your result.
The next_offset integer corresponds to the location of the next structure to consider.
It means that you will have to go at this location to retrieve the next corresponding character, then go to the next-next location, and so on.
note: clue is a kind of linked-list structure that you may have already studied in your algoritm course.
Finally, you will find a clue structure whose the next_offset field is -1.
It means that no more clue will be read.

So, you have to read the input file.
Write the characters you found in the clues into the output file (create it if needed).

Information
How to read a structure?
Well, do you remember the previous exercise?
You could convert binary data into C types. You can do the same thing by setting the right parameters to the fopen function.
We assume that the given input file will follow the two rules given above.
We also remind you that you should set the opening mode to "rb", as you will read binary data, and for portability purpose.

Code example(s)
struct clue {
  char letter;
  long next_offset;
};
Copy

Prototype(s)
int scavenger_hunt(char *input, char *output);
Copy

Code example(s)
int main()
{
    int a = scavenger_hunt("example.txt", "example.log");
    int b = scavenger_hunt("does_not_exist.txt", "does_not_exist.log");

    /* FLAG
    You will apply the algorithm to the folder you shuffled, hoping that it will
    give us the information sorted again...
    */
    int c = scavenger_hunt("flag.txt", "flag.log");
    
    printf("%d %d %d\n", a, b, c);
    // Expected: 0 1 0

    /*
    $ cat example.log
    You found me!

    $ cat flag.log
    ???
    */

    return 0;
}
Copy


proficiencies/decoder/decoder.c

Lore
You convinced the team that you indeed find the culprit!
So you look at the police files and... uh well.. it seems to be encoded.
The next and final step is thus to repair this file, and reveal his real identity!

The final file that you have to read is encoded!
Hopefully, you know the algorithm to get back its original version.
Open the file with the "rb" mode.
Read four bytes. We will call these bytes a "block".
Get the value of the fourth byte. (the right most byte of the block you've just read)
Shift every bit to the right, as much as the value computed at step 3 
+
8
+8.
Again, get the value of the fourth byte, and cast it to a char.
Print the char obtained.
If at least 4 bytes remain to be read, repeat from step 2.
If less than 4 bytes remain to be read, you can ignore them and exit the algorithm.

First of all, you need a function that executes steps 4 and 5.
It will take an int as parameter.
Why? Because an integer is encoded on 4 bytes, so it is exactly the size needed to send the block.

Prototype(s)
char decode_block(int block);
Copy

Code example(s)
int main()
{
    printf("%c\n", decode_block(67307523));
    /*
    Binary representation is:
    0000 0100 | 0000 0011 | 0000 1000 | 0000 0011
    Take the right-most byte: 0000 0011 = 3 in decimal
    
    Perform a shift of 0000 0011 +8 = 3+8 = 11 to the right:
    From: 0000 0100 | 0000 0011 | 0000 1000 | 0000 0011 -->
          ---------------------------------------------
    To:   xxxx xxxx | xxx0 0000 | 1000 0000 | 0110 0001 (00000000011)
          ^^^^ ^^^^   ^^^                                ^^^^^^^^^^^
          You can consider it as zeros.                  Useless to keep it

    Now, the right-most byte is: 0110 0001 = 97 in decimal = 'a' in ASCII
    
    So, expected result is:
    a
  */

    return 0;
}
Copy

Now that you are able to decode a block, you can wrap your function into a loop to execute the entire algorithm.

Prototype(s)
int print_decoded_file(const char *filename);
Copy

Code example(s)
int main()
{
    print_decoded_file("example.txt");
    /* Expected:
    You are on the right way!
    */

    print_decoded_file("does_not_exist.txt");
    // Nothing happens

    /* FLAG
    Decode this file, and you will discover the author of the biggest heist of
    the last century...
    */
    // print_decoded_file("flag.txt"); // <-- Uncomment it...

    return 0;
}
Copy

Information
The final flag is a huge file wich does not fit on the terminal...
Try to press [CTRL+-] or [CTR+SHIFT+-] to dezoom it.


https://en.wikipedia.org/wiki/Luhn_algorithm

This page and all subpages are for internal use at EPITA only.
The use of this document must abide by the following rules:
You are seeing it from the EPITA’ Gitlab pages.
This page and all subpages are strictly personal and must not be passed onto someone else.
Non-compliance with these rules can lead to severe sanctions.
Copyright © 2026-2027 - EPITA
