diff --git a/hdrs/chip8.h b/hdrs/chip8.h new file mode 100644 index 0000000..8df1777 --- /dev/null +++ b/hdrs/chip8.h @@ -0,0 +1,34 @@ +#ifndef CHIP8_H +# define CHIP8_H + +#include +#include "./memory.h" +#include "./graphics.h" +#include "./input.h" + +#define FRAME_TIME_MS 1000 / 60 + +typedef struct s_chip8 +{ + t_memory memory; + t_display display; + t_keyboard keyboard; + + uint8_t v[16]; + uint16_t i; + uint16_t pc; + + uint8_t delay_timer; + uint8_t sound_timer; + + uint16_t stack[16]; + uint8_t sp; + + int running; +} t_chip8; + +int chip8_load_rom(t_chip8 *emu, const char *path); +void chip8_init(t_chip8 *emu); +void chip8_run(t_chip8 *emu); + +#endif \ No newline at end of file diff --git a/hdrs/cpu.h b/hdrs/cpu.h new file mode 100644 index 0000000..58710a7 --- /dev/null +++ b/hdrs/cpu.h @@ -0,0 +1,23 @@ +#ifndef CPU_H +# define CPU_H + +#include "./chip8.h" +#include + +typedef struct s_instruction +{ + uint16_t opcode; + uint8_t type; + uint8_t x; + uint8_t y; + uint8_t n; + uint8_t nn; + uint16_t nnn; +} t_instruction; + + +uint16_t cpu_fetch(t_chip8 *emu); +t_instruction cpu_decode(uint16_t opcode); +void cpu_execute(t_chip8 *emu, t_instruction instruction); + +#endif \ No newline at end of file diff --git a/hdrs/font.h b/hdrs/font.h new file mode 100644 index 0000000..67f23ab --- /dev/null +++ b/hdrs/font.h @@ -0,0 +1,27 @@ +#ifndef FONT_H +#define FONT_H + +#define FONTSET_SIZE 80 +#define FONTSET_START (MEMORY_SIZE - FONTSET_SIZE) + +static const unsigned char FONTSET[FONTSET_SIZE] = { + 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 + 0x20, 0x60, 0x20, 0x20, 0x70, // 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, // A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B + 0xF0, 0x80, 0x80, 0x80, 0xF0, // C + 0xE0, 0x90, 0x90, 0x90, 0xE0, // D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E + 0xF0, 0x80, 0xF0, 0x80, 0x80 // F +}; + + +#endif diff --git a/hdrs/graphics.h b/hdrs/graphics.h new file mode 100644 index 0000000..3669e35 --- /dev/null +++ b/hdrs/graphics.h @@ -0,0 +1,29 @@ +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include +#include + +struct s_chip8; +typedef struct s_chip8 t_chip8; + +#define SCREEN_WIDTH 64 +#define SCREEN_HEIGHT 32 +#define SCALE 12 + +typedef struct s_display +{ + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + uint32_t pixels[SCREEN_WIDTH * SCREEN_HEIGHT]; +} t_display; + +int graphics_init(t_display *display); +void graphics_render(t_chip8 *emu); +void graphics_cleanup(t_display *display); +void graphics_draw_sprite(t_chip8 *emu, uint8_t x, uint8_t y, uint8_t n); +int graphics_display_ready(); + + +#endif \ No newline at end of file diff --git a/hdrs/input.h b/hdrs/input.h new file mode 100644 index 0000000..b50d5f9 --- /dev/null +++ b/hdrs/input.h @@ -0,0 +1,26 @@ +#ifndef INPUT_H +#define INPUT_H + +#include +#include + +#define KEY_COUNT 16 + +struct s_chip8; +typedef struct s_chip8 t_chip8; + +typedef struct s_keyboard +{ + uint8_t keyboard[KEY_COUNT]; + uint8_t waiting_for_key; + uint8_t key_pressed; + uint8_t key_register; +} t_keyboard; + + +void input_update(SDL_Event *event, uint8_t *keyboard); +uint8_t map_sdl_to_chip8(SDL_Keycode key); +uint8_t get_pressed_key(t_chip8 *emu); + + +#endif // INPUT_H diff --git a/hdrs/memory.h b/hdrs/memory.h new file mode 100644 index 0000000..7142f11 --- /dev/null +++ b/hdrs/memory.h @@ -0,0 +1,22 @@ +#ifndef MEMORY_H +# define MEMORY_H + +#include +#include +#include "graphics.h" + +#define MEMORY_SIZE 4096 + +typedef struct s_memory +{ + uint8_t memory[MEMORY_SIZE]; + uint8_t screen[SCREEN_WIDTH * SCREEN_HEIGHT]; +} t_memory; + +void memory_init(t_memory *mem); +int memory_load_rom(t_memory *mem, FILE *fp); +void memory_load_fontset(t_memory *mem); +void memory_clear_screen(t_memory *mem); +void memory_dump(t_memory *mem, const char *path); + +#endif \ No newline at end of file diff --git a/srcs/chip8.c b/srcs/chip8.c new file mode 100644 index 0000000..9059fab --- /dev/null +++ b/srcs/chip8.c @@ -0,0 +1,119 @@ +#include "chip8.h" +#include "memory.h" +#include "cpu.h" +#include "input.h" +#include "libft.h" + +#include +#include +#include +#include + +int chip8_load_rom(t_chip8 *emu, const char *path) +{ + FILE *fp = fopen(path, "rb"); + if (fp == NULL) + { + return 1; + } + + if (memory_load_rom(&emu->memory, fp) == 1) + { + fclose(fp); + return (1); + } + + fclose(fp); + return (0); +} + +void chip8_init(t_chip8 *emu) +{ + ft_memset(emu->keyboard.keyboard, 0, sizeof(emu->keyboard.keyboard)); + + memory_init(&emu->memory); + memory_load_fontset(&emu->memory); + + memset(emu->v, 0, sizeof(emu->v)); + memset(emu->stack, 0, sizeof(emu->stack)); + + emu->i = 0; + emu->pc = 0x200; + + emu->delay_timer = 0; + emu->sound_timer = 0; + + emu->sp = 0; + + if (graphics_init(&emu->display) != 0) + { + printf("Failed to initialize graphics!\n"); + exit(1); + } + + if (graphics_display_ready() != 1) + { + printf("Graphics display failed to initialize properly!\n"); + exit(1); + } + + emu->running = 1; +} + +void chip8_run(t_chip8 *emu) +{ + uint32_t frame_start, frame_end, frame_duration; + SDL_Event event; + + while (emu->running) + { + frame_start = SDL_GetTicks(); + + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + emu->running = 0; + break; + } + if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) + { + input_update(&event, emu->keyboard.keyboard); + } + } + + if (emu->keyboard.waiting_for_key) + { + uint8_t key = get_pressed_key(emu); + if (key != 0xFF) + { + emu->v[emu->keyboard.key_register] = key; + emu->keyboard.waiting_for_key = 0; + } + } + else + { + for (int i = 0; i < 10; i++) + { + uint16_t opcode = cpu_fetch(emu); + t_instruction instruction = cpu_decode(opcode); + cpu_execute(emu, instruction); + } + } + + if (emu->delay_timer > 0) + emu->delay_timer--; + if (emu->sound_timer > 0) + emu->sound_timer--; + + graphics_render(emu); + + frame_end = SDL_GetTicks(); + frame_duration = frame_end - frame_start; + + if (frame_duration < FRAME_TIME_MS) + { + SDL_Delay(FRAME_TIME_MS - frame_duration); + } + } +} diff --git a/srcs/cpu.c b/srcs/cpu.c new file mode 100644 index 0000000..fb7aae0 --- /dev/null +++ b/srcs/cpu.c @@ -0,0 +1,257 @@ +#include "input.h" +#include "cpu.h" +#include "font.h" +#include +#include + +#define OPCODE_SIZE 2 + +uint16_t cpu_fetch(t_chip8 *emu) +{ + // Check if there is enough space in memory to fetch a 2-byte opcode + if (emu->pc + 1 >= MEMORY_SIZE) + { + printf("Error, program counter out of bounds! (pc = 0x%04X)\n", emu->pc); + graphics_cleanup(&emu->display); + emu->running = 0; + return (1); + } + + uint16_t opcode = (emu->memory.memory[emu->pc] << 8) | emu->memory.memory[emu->pc + 1]; + return opcode; +} + +t_instruction cpu_decode(uint16_t opcode) +{ + t_instruction instruction; + + instruction.opcode = opcode; + instruction.type = (opcode & 0xF000) >> 12; + instruction.x = (opcode & 0x0F00) >> 8; + instruction.y = (opcode & 0x00F0) >> 4; + instruction.n = opcode & 0x000F; + instruction.nn = opcode & 0x00FF; + instruction.nnn = opcode & 0x0FFF; + + return instruction; +} +void cpu_execute(t_chip8 *emu, t_instruction instruction) +{ + uint8_t x = instruction.x; + uint8_t y = instruction.y; + uint8_t n = instruction.n; + uint8_t nn = instruction.nn; + uint16_t nnn = instruction.nnn; + + switch (instruction.type) + { + // 0x0NNN: SYS addr (ignored, do nothing) + case 0x0: + // 0x00E0: CLS (Clear screen) + if (instruction.opcode == 0x00E0) + memory_clear_screen(&emu->memory); + // 0x00EE: RET (Return from subroutine) + else if (instruction.opcode == 0x00EE) { + if (emu->sp == 0) + { + printf("Stack underflow on RET!\n"); + emu->running = 0; + return; + } + emu->pc = emu->stack[--emu->sp]; + } + break; + + // 0x1NNN: JP addr (Jump to address NNN) + case 0x1: + emu->pc = nnn; + return; // Do not increment PC after jump + + // 0x2NNN: CALL addr (Call subroutine at NNN) + case 0x2: + if (emu->sp >= 16) + { + printf("Stack overflow!\n"); + emu->running = 0; + return; + } + emu->stack[emu->sp++] = emu->pc; + emu->pc = nnn; + return; // Do not increment PC after call + + // 0x3XNN: SE Vx, byte (Skip if Vx == NN) + case 0x3: + if (emu->v[x] == nn) + emu->pc += OPCODE_SIZE; + break; + + // 0x4XNN: SNE Vx, byte (Skip if Vx != NN) + case 0x4: + if (emu->v[x] != nn) + emu->pc += OPCODE_SIZE; + break; + + // 0x5XY0: SE Vx, Vy (Skip if Vx == Vy) + case 0x5: + if (n == 0 && emu->v[x] == emu->v[y]) + emu->pc += OPCODE_SIZE; + break; + + // 0x6XNN: LD Vx, byte (Load NN into Vx) + case 0x6: + emu->v[x] = nn; + break; + + // 0x7XNN: ADD Vx, byte (Add NN to Vx) + case 0x7: + emu->v[x] += nn; + break; + + // 0x8XYN: Arithmetic opcodes (switch on last nibble, 'n') + case 0x8: + switch (n) + { + // 0x8XY0: LD Vx, Vy (Set Vx = Vy) + case 0x0: + emu->v[x] = emu->v[y]; + break; + // 0x8XY1: OR Vx, Vy (Set Vx = Vx OR Vy) + case 0x1: + emu->v[x] |= emu->v[y]; + break; + // 0x8XY2: AND Vx, Vy (Set Vx = Vx AND Vy) + case 0x2: + emu->v[x] &= emu->v[y]; + break; + // 0x8XY3: XOR Vx, Vy (Set Vx = Vx XOR Vy) + case 0x3: + emu->v[x] ^= emu->v[y]; + break; + // 0x8XY4: ADD Vx, Vy (Set Vx = Vx + Vy, set VF = carry) + case 0x4: + emu->v[0xF] = (emu->v[x] + emu->v[y]) > 0xFF; + emu->v[x] += emu->v[y]; + break; + // 0x8XY5: SUB Vx, Vy (Set Vx = Vx - Vy, set VF = NOT borrow) + case 0x5: + emu->v[0xF] = emu->v[x] >= emu->v[y]; + emu->v[x] -= emu->v[y]; + break; + // 0x8XY6: SHR Vx, Vy (Set Vx = Vx >> 1, set VF = LSB before shift) + case 0x6: + emu->v[0xF] = emu->v[x] & 1; + emu->v[x] >>= 1; + break; + // 0x8XY7: SUBN Vx, Vy (Set Vx = Vy - Vx, set VF = NOT borrow) + case 0x7: + emu->v[0xF] = emu->v[y] >= emu->v[x]; + emu->v[x] = emu->v[y] - emu->v[x]; + break; + // 0x8XYE: SHL Vx, Vy (Set Vx = Vx << 1, set VF = MSB before shift) + case 0xE: + emu->v[0xF] = (emu->v[x] >> 7) & 1; + emu->v[x] <<= 1; + break; + default: + printf("Unknown arithmetic opcode: 0x%04X\n", instruction.opcode); + break; + } + break; + + // 0x9XY0: SNE Vx, Vy (Skip if Vx != Vy) + case 0x9: + if (n == 0 && emu->v[x] != emu->v[y]) + emu->pc += OPCODE_SIZE; + break; + + // 0x0ANNN: LD I, addr (Set I = NNN) + case 0xA: + emu->i = nnn; + break; + + // 0xBNNN: JP V0, addr (Jump to NNN + V0) + case 0xB: + emu->pc = nnn + emu->v[0]; + return; // Do not increment PC after jump + + // 0xCXNN: RND Vx, byte (Set Vx = random byte AND NN) + case 0xC: + emu->v[x] = (rand() & 0xFF) & nn; + break; + + // 0xDXYN: DRW Vx, Vy, nibble (Draw sprite at (Vx, Vy), N rows) + // N=0 means 16 rows (wrapping) + case 0xD: + graphics_draw_sprite(emu, emu->v[x] % 64, emu->v[y] % 32, n); + break; + + // 0xEX9E: SKP Vx (Skip if key Vx is pressed) + // 0xEXA1: SKNP Vx (Skip if key Vx is not pressed) + case 0xE: + if (nn == 0x9E) + { + if (emu->keyboard.keyboard[emu->v[x] & 0xF]) + emu->pc += OPCODE_SIZE; + } + else if (nn == 0xA1) + { + if (!emu->keyboard.keyboard[emu->v[x] & 0xF]) + emu->pc += OPCODE_SIZE; + } + break; + + // 0xFXNN: Various instructions (switch on NN) + case 0xF: + switch (nn) + { + // 0xFX07: LD Vx, DT (Set Vx = delay timer) + case 0x07: + emu->v[x] = emu->delay_timer; + break; + // 0xFX0A: LD Vx, K (Wait for key press, store in Vx) + case 0x0A: + emu->keyboard.waiting_for_key = 1; + emu->keyboard.key_register = x; + break; + // 0xFX15: LD DT, Vx (Set delay timer = Vx) + case 0x15: + emu->delay_timer = emu->v[x]; + break; + // 0xFX18: LD ST, Vx (Set sound timer = Vx) + case 0x18: + emu->sound_timer = emu->v[x]; + break; + // 0xFX1E: ADD I, Vx (Set I = I + Vx) + case 0x1E: + emu->i += emu->v[x]; + break; + // 0xFX29: LD F, Vx (Set I = font sprite address for character in Vx) + case 0x29: + // emu->i = 0x50 + 5 * (emu->v[x] & 0xF); + emu->i = FONTSET_START + 5 * (emu->v[x] & 0xF); + break; + // 0xFX33: LD B, Vx (Store BCD representation of Vx at I, I+1, I+2) + case 0x33: + emu->memory.memory[emu->i] = (emu->v[x] / 100) % 10; + emu->memory.memory[emu->i + 1] = (emu->v[x] / 10) % 10; + emu->memory.memory[emu->i + 2] = emu->v[x] % 10; + break; + // 0xFX55: LD [I], Vx (Store V0-Vx in memory starting at I) + case 0x55: + for (int i = 0; i <= x; i++) + emu->memory.memory[emu->i + i] = emu->v[i]; + break; + // 0xFX65: LD Vx, [I] (Load V0-Vx from memory starting at I) + case 0x65: + for (int i = 0; i <= x; i++) + emu->v[i] = emu->memory.memory[emu->i + i]; + break; + default: + printf("Unknown 0xF opcode: 0x%04X\n", instruction.opcode); + break; + } + break; + } + + emu->pc += OPCODE_SIZE; +} diff --git a/srcs/graphics.c b/srcs/graphics.c new file mode 100644 index 0000000..ee4e8b8 --- /dev/null +++ b/srcs/graphics.c @@ -0,0 +1,122 @@ +#include "graphics.h" +#include +#include +#include +#include "chip8.h" + +int graphics_init(t_display *display) +{ + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + fprintf(stderr, "SDL2 could not initiallize! e=%s\n", SDL_GetError()); + return (1); + } + + display->window = SDL_CreateWindow("c8", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + SCREEN_WIDTH * SCALE, + SCREEN_HEIGHT * SCALE, + SDL_WINDOW_SHOWN); + if (!display->window) + { + fprintf(stderr, "Window could not be created! e=%s\n", SDL_GetError()); + return (1); + } + + display->renderer = SDL_CreateRenderer(display->window, -1, SDL_RENDERER_ACCELERATED); + if (!display->renderer) + { + fprintf(stderr, "Renderer could not be created! e=%s\n", SDL_GetError()); + return (1); + } + + display->texture = SDL_CreateTexture(display->renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + SCREEN_WIDTH, + SCREEN_HEIGHT); + if (!display->texture) + { + fprintf(stderr, "Texture could not be created! e=%s\n", SDL_GetError()); + return (1); + } + + return (0); +} + +void graphics_render(t_chip8 *emu) +{ + for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) + { + uint8_t pixel = emu->memory.screen[i]; + emu->display.pixels[i] = pixel ? 0xFFFFFFFF : 0xFF000000; + } + + SDL_UpdateTexture(emu->display.texture, NULL, emu->display.pixels, SCREEN_WIDTH * sizeof(uint32_t)); + + SDL_RenderClear(emu->display.renderer); + SDL_RenderCopy(emu->display.renderer, emu->display.texture, NULL, NULL); + SDL_RenderPresent(emu->display.renderer); +} + +void graphics_cleanup(t_display *display) +{ + SDL_DestroyTexture(display->texture); + SDL_DestroyRenderer(display->renderer); + SDL_DestroyWindow(display->window); + SDL_Quit(); +} +void graphics_draw_sprite(t_chip8 *emu, uint8_t x, uint8_t y, uint8_t n) +{ + uint8_t pixel; + uint16_t sprite_address = emu->i; + + uint8_t collision = 0; + + for (int row = 0; row < n; row++) + { + pixel = emu->memory.memory[sprite_address + row]; + + for (int col = 0; col < 8; col++) + { + if ((pixel & (0x80 >> col)) != 0) + { + uint16_t screen_x = (x + col) % SCREEN_WIDTH; + uint16_t screen_y = (y + row) % SCREEN_HEIGHT; + + if (emu->memory.screen[screen_y * SCREEN_WIDTH + screen_x] == 1) + { + collision = 1; + } + + emu->memory.screen[screen_y * SCREEN_WIDTH + screen_x] ^= 1; + } + } + } + + emu->v[0xF] = collision; +} + +int graphics_display_ready() +{ + SDL_Event event; + + while (1) + { + while (SDL_PollEvent(&event) != 0) + { + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SHOWN) + { + return 1; + } + + // Check for window close event + if (event.type == SDL_QUIT) + { + return 0; + } + } + + SDL_Delay(10); + } +} diff --git a/srcs/input.c b/srcs/input.c new file mode 100644 index 0000000..9ff2e25 --- /dev/null +++ b/srcs/input.c @@ -0,0 +1,60 @@ +#include "input.h" +#include "chip8.h" +#include + +void input_update(SDL_Event *event, uint8_t *keyboard) +{ + uint8_t key = map_sdl_to_chip8(event->key.keysym.sym); + if (key != 0xFF) + { + if (event->type == SDL_KEYDOWN) + { + keyboard[key] = 1; + } + else if (event->type == SDL_KEYUP) + { + keyboard[key] = 0; + } + } +} + + +uint8_t map_sdl_to_chip8(SDL_Keycode key) +{ + // Map to a numpad-like layout: + // 1 2 3 4 → 1 2 3 C + // Q W E R → 4 5 6 D + // A S D F → 7 8 9 E + // Z X C V → A 0 B F + switch (key) + { + case SDLK_1: return 0x1; + case SDLK_2: return 0x2; + case SDLK_3: return 0x3; + case SDLK_4: return 0xC; + case SDLK_q: return 0x4; + case SDLK_w: return 0x5; + case SDLK_e: return 0x6; + case SDLK_r: return 0xD; + case SDLK_a: return 0x7; + case SDLK_s: return 0x8; + case SDLK_d: return 0x9; + case SDLK_f: return 0xE; + case SDLK_z: return 0xA; + case SDLK_x: return 0x0; + case SDLK_c: return 0xB; + case SDLK_v: return 0xF; + default: return 0xFF; + } +} + + +uint8_t get_pressed_key(t_chip8 *emu) +{ + for (int i = 0; i < 16; i++) + { + if (emu->keyboard.keyboard[i]) + return i; + } + return 0xFF; // No key pressed +} diff --git a/srcs/main.c b/srcs/main.c new file mode 100644 index 0000000..b7d93f3 --- /dev/null +++ b/srcs/main.c @@ -0,0 +1,25 @@ +#include +#include "../hdrs/chip8.h" + +int main(int argc, char **argv) +{ + if (argc != 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return (1); + } + + t_chip8 emu; + + chip8_init(&emu); + + if (chip8_load_rom(&emu, argv[1]) == 1) + { + fprintf(stderr, "Failed to load rom: %s\n", argv[1]); + return (1); + } + + chip8_run(&emu); + + return (0); +} \ No newline at end of file diff --git a/srcs/memory.c b/srcs/memory.c new file mode 100644 index 0000000..7b40af1 --- /dev/null +++ b/srcs/memory.c @@ -0,0 +1,54 @@ +#include "../hdrs/memory.h" +#include +#include +#include "../hdrs/font.h" +#include "libft.h" + +void memory_init(t_memory *mem) +{ + ft_memset(mem->memory, 0, MEMORY_SIZE); +} +int memory_load_rom(t_memory *mem, FILE *fp) +{ + fseek(fp, 0, SEEK_END); + size_t rom_size = ftell(fp); + rewind(fp); + + if (rom_size > (sizeof(mem->memory) - 0x200)) + { + return (1); + } + + if (fread(&mem->memory[0x200], 1, rom_size, fp) != rom_size) + { + return (1); + } + + return (0); +} + +void memory_clear_screen(t_memory *mem) +{ + ft_memset(mem->screen, 0, SCREEN_WIDTH * SCREEN_HEIGHT); +} + +void memory_load_fontset(t_memory *mem) +{ + ft_memcpy(&mem->memory[MEMORY_SIZE - FONTSET_SIZE], FONTSET, FONTSET_SIZE); +} + + +void memory_dump(t_memory *mem, const char *path) +{ + FILE *fp = fopen(path, "wb"); + + if (!fp) + { + printf("Failed to open: %s\n", path); + exit(1); + } + + fwrite(mem->memory, 1, MEMORY_SIZE, fp); + fclose(fp); + printf("Memory dumped to: %s\n", path); +} \ No newline at end of file