commit c5f1fd676c7c64497e1c8ec82b4850ffac9daf98 Author: IllusionMan1212 Date: Mon Mar 8 01:05:41 2021 +0200 initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3b9a717 --- /dev/null +++ b/.clang-format @@ -0,0 +1,100 @@ +--- +Language: Cpp +# BasedOnStyle: WebKit +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e44be4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +roms/ +Makefile diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..695452c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/chip8debug", + "args": ["IBM Logo.ch8"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1e256cb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,50 @@ +{ + "cmake.configureOnOpen": true, + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "bit": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1568d5c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) + +project(chip8 VERSION 0.1) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CSS_STANDARD_REQUIRED True) +set(CMAKE_DEBUG_POSTFIX debug) + +add_executable(chip8 main.cpp) +set_target_properties(chip8 PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + +find_package(sdl2 REQUIRED) +target_link_libraries(chip8 SDL2) + +configure_file(version.h.in version.h) + +target_include_directories(chip8 PUBLIC "${PROJECT_BINARY_DIR}") \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..86c05ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ + +MIT License + +Copyright (c) 2021 IllusionMan1212 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/chip8.h b/chip8.h new file mode 100644 index 0000000..6bbec85 --- /dev/null +++ b/chip8.h @@ -0,0 +1,32 @@ +#include + +struct Chip8 { + uint8_t V[16]; + uint16_t I; + uint16_t PC = 0x200; + uint16_t SP; + uint16_t stack[16]; + uint8_t memory[4096]; + + uint32_t pixels[64 * 32]; + bool draw; +}; + +static const uint8_t font[80] = { + 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 +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ca4967d --- /dev/null +++ b/main.cpp @@ -0,0 +1,307 @@ +#include + +#include "chip8.h" +#include "version.h" + +#include +#include + +void poll_events(SDL_Window *window, bool *running) { + SDL_Event ev; + + while (SDL_PollEvent(&ev)) { + switch (ev.type) { + case SDL_QUIT: + *running = false; + break; + case SDL_KEYDOWN: + switch (ev.key.keysym.sym) { + case SDLK_ESCAPE: + *running = false; + break; + } + break; + case SDL_WINDOWEVENT: + switch (ev.window.event) { + case SDL_WINDOWEVENT_RESIZED: + break; + } + } + } +} + +void emulate(Chip8 &chip8, SDL_Window *window) { + constexpr uint32_t ON_COLOR = 0xFFFFFFFF; + constexpr uint32_t OFF_COLOR = 0x000000FF; + const uint8_t first_byte = chip8.memory[chip8.PC]; + const uint8_t second_byte = chip8.memory[chip8.PC + 1]; + const uint16_t opcode = first_byte << 8 | second_byte; + + printf("Executing %02X%02X \n", first_byte, second_byte); + + switch (first_byte >> 4) { + case 0x0: + switch (second_byte) { + case 0xE0: { + // clear the screen with a black color + std::fill_n(chip8.pixels, 64 * 32, OFF_COLOR); + chip8.PC += 2; + break; + } + case 0xEE: + // return from a subroutine + chip8.SP--; + chip8.PC = chip8.stack[chip8.SP]; + chip8.PC += 2; + break; + } + break; + case 0x1: + chip8.PC = ((first_byte & 0x0F) << 8) | second_byte; + break; + case 0x2: + // CALL subroutine + chip8.stack[chip8.SP] = chip8.PC; + chip8.SP = (chip8.SP + 1); + chip8.PC = ((first_byte & 0x0F) << 8) | second_byte; + break; + case 0x3: + if (chip8.V[first_byte & 0x0F] == second_byte) { + chip8.PC += 2; + } + chip8.PC += 2; + break; + case 0x4: + if (chip8.V[first_byte & 0x0F] != second_byte) { + chip8.PC += 2; + } + chip8.PC += 2; + break; + case 0x5: + if (chip8.V[first_byte & 0x0F] == chip8.V[(second_byte & 0xF0) >> 4]) { + chip8.PC += 2; + } + chip8.PC += 2; + break; + case 0x6: + chip8.V[first_byte & 0x0F] = second_byte; + chip8.PC += 2; + break; + case 0x7: + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] + second_byte; + chip8.PC += 2; + break; + case 0x8: + switch (second_byte & 0x0F) { + case 0x0: + chip8.V[first_byte & 0x0F] = chip8.V[(second_byte & 0xF0) >> 4]; + chip8.PC += 2; + break; + case 0x1: + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] | chip8.V[(second_byte & 0xF0) >> 4]; + chip8.PC += 2; + break; + case 0x2: + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] & chip8.V[(second_byte & 0xF0) >> 4]; + chip8.PC += 2; + break; + case 0x3: + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] ^ chip8.V[(second_byte & 0xF0) >> 4]; + chip8.PC += 2; + break; + case 0x4: + if ((chip8.V[first_byte & 0x0F] + chip8.V[(second_byte & 0xF0) >> 4]) > 0xFF) { + chip8.V[0xF] = 1; + } else { + chip8.V[0xF] = 0; + } + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] + chip8.V[(second_byte & 0xF0) >> 4]; + chip8.PC += 2; + break; + case 0x5: + // set Vx = Vx - Vy, set VF = NOT borrow + if (chip8.V[first_byte & 0x0F] > chip8.V[(second_byte & 0xF0) >> 4]) { + chip8.V[0xF] = 1; + } else { + chip8.V[0xF] = 0; + } + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] - chip8.V[(second_byte & 0xF0) >> 4]; + chip8.PC += 2; + break; + case 0x6: + if (chip8.V[first_byte & 0x0F] & 1 == 1) { + chip8.V[0xF] = 1; + } else { + chip8.V[0xF] = 0; + } + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] >> 1; + chip8.PC += 2; + break; + case 0x7: + // set Vx = Vy - Vx, set VF = NOT borrow + if (chip8.V[(second_byte & 0xF0) >> 4] > chip8.V[first_byte & 0x0F]) { + chip8.V[0xF] = 1; + } else { + chip8.V[0xF] = 0; + } + chip8.V[first_byte & 0x0F] = chip8.V[(second_byte & 0xF0) >> 4] - chip8.V[first_byte & 0x0F]; + chip8.PC += 2; + break; + case 0xE: + chip8.V[0xF] = (chip8.V[(opcode & 0x0F00) >> 8] >> 7) & 1; + chip8.V[first_byte & 0x0F] = chip8.V[first_byte & 0x0F] << 1; + chip8.PC += 2; + break; + } + break; + case 0x9: + if (chip8.V[first_byte & 0x0F] != chip8.V[(second_byte & 0xF0) >> 4]) { + chip8.PC += 2; + } + chip8.PC += 2; + break; + case 0xA: + chip8.I = ((first_byte & 0x0F) << 8) | second_byte; + chip8.PC += 2; + break; + case 0xB: + chip8.PC = (((first_byte & 0x0F) << 8) | second_byte) + chip8.V[0]; + break; + case 0xC: + chip8.V[first_byte & 0x0F] = (rand() & 0x00FF) & second_byte; + chip8.PC += 2; + break; + case 0xD: { + int height = second_byte & 0x0F; + chip8.V[0xF] = 0; + for (int y = 0; y < height; y++) { + uint8_t pixel = chip8.memory[chip8.I + y]; + for (int x = 0; x < 8; x++) { + if (pixel & (0x80 >> x)) { + // check if we're turning off the pixel and set VF. + int index = (chip8.V[first_byte & 0x0F] + x) % 64 + ((chip8.V[((second_byte & 0xF0) >> 4)] + y) % 32) * 64; + if (chip8.pixels[index] == ON_COLOR) { + chip8.V[0xF] = 1; + chip8.pixels[index] = OFF_COLOR; + } + chip8.pixels[index] = ON_COLOR; + } + } + } + chip8.draw = true; + chip8.PC += 2; + break; + } + case 0xE: + // TODO: + chip8.PC += 2; + break; + case 0xF: + auto X = (opcode & 0x0F00) >> 8; + switch (second_byte) { + case 0x07: + // TODO: + break; + case 0x0A: + // TODO: + break; + case 0x15: + // TODO: + break; + case 0x18: + // TODO: + break; + case 0x1E: + chip8.I = chip8.I + chip8.V[X]; + break; + case 0x29: + chip8.I = chip8.V[X] * 5; + break; + case 0x33: + chip8.memory[chip8.I] = (chip8.V[X]) / 100; + chip8.memory[chip8.I + 1] = (chip8.V[X] % 100) / 10; + chip8.memory[chip8.I + 2] = chip8.V[X] % 10; + break; + case 0x55: { + for (int i = 0; i <= X; i++) { + chip8.memory[chip8.I + i] = chip8.V[i]; + } + break; + } + case 0x65: { + for (int i = 0; i <= X; i++) { + chip8.V[i] = chip8.memory[chip8.I + i]; + } + break; + } + } + chip8.PC += 2; + break; + } +} + +int main(int argc, char **argv) { + if (argc < 2) { + std::cout << "No file was provided as input, quitting program" << std::endl; + return 0; + } + + std::ifstream file(argv[1], std::ios::binary | std::ios::ate); + + if (!file) { + std::cout << "Invalid file was provided, quitting program" << std::endl; + return 0; + } + + SDL_Window *window = nullptr; + SDL_Renderer *renderer = nullptr; + SDL_Texture *texture = nullptr; + + char title[64]; + sprintf(title, "Chip-8 v%d.%d [%s]", VERSION_MAJOR, VERSION_MINOR, argv[1]); + + Chip8 chip8; + + memcpy(chip8.memory, font, sizeof(font)); + memset(chip8.pixels, 0xFF000000, sizeof(chip8.pixels)); + memset(chip8.stack, 0, sizeof(chip8.stack)); + memset(chip8.V, 0, sizeof(chip8.V)); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + std::cout << "Video init failed"; + return -1; + } + + srand(time(0)); + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + file.read(reinterpret_cast(&chip8.memory) + 0x200, size); + + window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 320, SDL_WINDOW_SHOWN); + renderer = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGRA8888, SDL_TEXTUREACCESS_STATIC, 64, 32); + + bool running = true; + + while (running) { + emulate(chip8, window); + + if (chip8.draw) { + SDL_UpdateTexture(texture, 0, chip8.pixels, 64 * sizeof(uint32_t)); + SDL_RenderCopy(renderer, texture, 0, 0); + SDL_RenderPresent(renderer); + + chip8.draw = false; + } + poll_events(window, &running); + } + + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} \ No newline at end of file diff --git a/version.h b/version.h new file mode 100644 index 0000000..03ca65f --- /dev/null +++ b/version.h @@ -0,0 +1,2 @@ +#define VERSION_MAJOR 0 +#define VERSION_MINOR 1 diff --git a/version.h.in b/version.h.in new file mode 100644 index 0000000..3308a16 --- /dev/null +++ b/version.h.in @@ -0,0 +1,2 @@ +#define VERSION_MAJOR @chip8_VERSION_MAJOR@ +#define VERSION_MINOR @chip8_VERSION_MINOR@ \ No newline at end of file