<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <title>GBForth</title> <style type="text/css"> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #222; font-size: 100%; } .slide { position: absolute; top: 0; bottom: 0; left: 0; right: 0; background-color: #f7f7f7; } .slide-content { width: 800px; height: 600px; overflow: hidden; margin: 80px auto 0 auto; padding: 30px; font-weight: 200; font-size: 200%; line-height: 1.375; } .controls { position: absolute; bottom: 20px; left: 20px; } .arrow { width: 0; height: 0; border: 30px solid #333; float: left; margin-right: 30px; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .prev { border-top-color: transparent; border-bottom-color: transparent; border-left-color: transparent; border-left-width: 0; border-right-width: 50px; } .next { border-top-color: transparent; border-bottom-color: transparent; border-right-color: transparent; border-left-width: 50px; border-right-width: 0; } .prev:hover { border-right-color: #888; cursor: pointer; } .next:hover { border-left-color: #888; cursor: pointer; } h1 { font-size: 300%; line-height: 1.2; text-align: center; margin: 170px 0 0; } h2 { font-size: 100%; line-height: 1.2; margin: 5px 0; text-align: center; font-weight: 200; } h3 { font-size: 140%; line-height: 1.2; border-bottom: 1px solid #aaa; margin: 0; padding-bottom: 15px; } ul { padding: 20px 0 0 60px; font-weight: 200; line-height: 1.375; } .author h1 { font-size: 170%; font-weight: 200; text-align: center; margin-bottom: 30px; } .author h3 { font-weight: 100; text-align: center; font-size: 95%; border: none; } a { text-decoration: none; color: #44a4dd; } a:hover { color: #66b5ff; } pre { font-size: 60%; line-height: 1.3; } .progress { position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 1; } .progress-bar { width: 0%; height: 3px; background-color: #b4b4b4; -webkit-transition: width 0.05s ease-out; -moz-transition: width 0.05s ease-out; -o-transition: width 0.05s ease-out; transition: width 0.05s ease-out; } .hidden { display: none; } @media (max-width: 850px) { body { font-size: 70%; } .slide-content { width: auto; } img { width: 100%; } h1 { margin-top: 120px; } .prev, .prev:hover { border-right-color: rgba(135, 135, 135, 0.5); } .next, .next:hover { border-left-color: rgba(135, 135, 135, 0.5); } } @media (max-width: 480px) { body { font-size: 50%; overflow: hidden; } .slide-content { padding: 10px; margin-top: 10px; height: 340px; } h1 { margin-top: 50px; } ul { padding-left: 25px; } } @media print { * { -webkit-print-color-adjust: exact; } @page { size: letter; } .hidden { display: inline; } html { width: 100%; height: 100%; overflow: visible; } body { margin: 0 auto !important; border: 0; padding: 0; float: none !important; overflow: visible; background: none !important; font-size: 52%; } .progress, .controls { display: none; } .slide { position: static; } .slide-content { border: 1px solid #222; margin-top: 0; margin-bottom: 40px; height: 3.5in; overflow: visible; } .slide:nth-child(even) { /* 2 slides per page */ page-break-before: always; } } /* github.com style (c) Vasily Polovnyov <vast@whiteants.net> */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hljs-comment, .hljs-quote { color: #998; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst { color: #333; font-weight: bold; } .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: #008080; } .hljs-string, .hljs-doctag { color: #d14; } .hljs-title, .hljs-section, .hljs-selector-id { color: #900; font-weight: bold; } .hljs-subst { font-weight: normal; } .hljs-type, .hljs-class .hljs-title { color: #458; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { color: #000080; font-weight: normal; } .hljs-regexp, .hljs-link { color: #009926; } .hljs-symbol, .hljs-bullet { color: #990073; } .hljs-built_in, .hljs-builtin-name { color: #0086b3; } .hljs-meta { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,300); body { -webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; background-color: #FFFFFF; color: #242731; font-family: "Open Sans", sans-serif; font-size: 1em; } .slide { overflow: hidden; } .slide-content { overflow: visible; } #slide-1 h1, .dark h1 { margin: 170px 0 0; } h1 { font-size: 250%; margin: auto; } h1, h2, h3, h4, h5, h6 { font-weight: 700; letter-spacing: -0.5px; } h1, h2, h3 { color: #FF5544; } h3 { border-bottom: 1px solid rgba(36,39,49,.4); } h4, h5 { color: #000000; margin-bottom: 0; } h6 { margin-bottom: 0; text-transform: uppercase; } h4 + p, h5 + p, h6 + p, h4 + ul, h5 + ul, h6 + ul { margin-top: 0; } a { color: #61C5AA; text-decoration: underline; } a:hover { color: #44b999; text-decoration: none; } ul { list-style: none; } ul > li { margin: -0.2em 0; } ul > li:before { content: ""; border-color: transparent rgba(36,39,49,.4); border-style: solid; border-width: 0.2em 0 0.2em 0.3em; display: block; left: -0.6em; top: 0.95em; position: relative; margin: 0px; padding: 0px; height: 0px; width: 0px; } li > ul, li > ol { padding: 0 0 0 1.5em; } li > ul > li:before { opacity: 0.5; } pre, code { font-family: "Monaco", monospace; } p > code, li > code { color: #FF5544; background-color: #FFFFFF; padding: 0.2em 0.4em; font-size: 60%; border: 1px solid rgba(0, 0, 0, 0.1); } pre, blockquote, img { padding: 1em; background-color: #FFFFFF; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); } img { padding: 0.5em; display: block; margin-left: auto; margin-right: auto; width: 100%; } img + em { display: block; margin-top: 0.5em; text-align: center; font-size: 50%; } blockquote > p:first-child { margin-top: 0; } blockquote > p:last-child { margin-bottom: 0; } blockquote { border-left: 3px solid #FF5544; } img[alt="background"] { padding: 0; margin: 0; box-shadow: none; position: absolute; z-index: 5; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.05; } #slide-1 img[alt="background"] { opacity: 0.08; } #slide-1 { background-color: #FF5544; color: #FFFFFF; } .slide.dark { background-color: #222222; color: #FFFFFF; } #slide-1 h1, #slide-1 h2, .slide.dark h1, .slide.dark h2 { color: #FFFFFF; text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2); position: relative; z-index: 10; } #slide-1 h2, .slide.dark h2 { opacity: 0.75; } .slide.dark ul > li:before { border-color: transparent rgba(255,255,255,.6); } .author-slide { background-color: #737373; } .author-slide h1 { color: #FFFFFF; } .author-slide, .slide.dark a { color: #FF5544; position: relative; z-index: 10; } .progress { z-index: 100; } .progress-bar { background-color: #FF5544; transition-duration: 0.25s; } </style> </head> <body> <div class="progress"> <div class="progress-bar"></div> </div> <div class="slide" id="slide-1"> <section class="slide-content"><h1 id="gbforth">GBForth</h1> <h2 id="a-forth-based-game-boy-development-kit">A Forth-based Game Boy development kit</h2> <p><img src="gbschematic.jpg" alt="background"></p> </section> </div> <div class="slide hidden" id="slide-2"> <section class="slide-content"><h3 id="game-boy-hardware">Game Boy hardware</h3> <ul> <li>8-bit CPU</li> <li>4 MHz (~1M instructions)</li> <li>32kB ROM (of which 16 bankable)</li> <li>4kB RAM</li> <li>Cartridge contains data + hardware</li> </ul> <p><img src="memmap.gif" alt="memmap"> <em><span style="color: #EB3223">ROM</span> <span style="background-color: #72FBFD">Tile RAM</span> <span style="color: #0022F5;">BG Map</span> <span style="background-color: #FFFD54;">RAM</span> <span style="color: #CC36D8">OAM</span></em></p> </section> </div> <div class="slide hidden" id="slide-3"> <section class="slide-content"><p><img src="draw.png" alt="draw"></p> </section> </div> <div class="slide hidden" id="slide-4"> <section class="slide-content"><h3 id="forth">Forth</h3> <ul> <li>Stack based</li> <li>Concatenative</li> </ul> <pre><code class="lang-fs">: INC <span class="hljs-number">1</span> + ; </code></pre> <p>Only <strong>numbers</strong> and <strong>words</strong>:</p> <pre><code>1 → PUSH 1 + → CALL + </code></pre></section> </div> <div class="slide hidden" id="slide-5"> <section class="slide-content"><h1 id="approaches-we-considered">Approaches we considered</h1> <ul> <li>Read docs about GB</li> <li>Start writing a compiler</li> <li>...wait forever until you get something on the screen</li> </ul> </section> </div> <div class="slide hidden" id="slide-6"> <section class="slide-content"><h1 id="however-">However...</h1> <ul> <li>Not incremental</li> <li>Long feedback cycle</li> </ul> <h1 id="-">😕</h1> </section> </div> <div class="slide hidden" id="slide-7"> <section class="slide-content"><h1 id="our-approach-">Our approach ✨</h1> <ul> <li>Start with working game (binary)</li> <li>Make Forth emit those bytes</li> <li>Reverse engineer + refactor bytes<ul> <li>Add abstractions</li> <li>Build libraries</li> </ul> </li> <li>Forth compiler → GB binary</li> </ul> </section> </div> <div class="slide hidden" id="slide-8"> <section class="slide-content"><p><img src="helloworld.png" alt="helloworld"></p> </section> </div> <div class="slide hidden" id="slide-9"> <section class="slide-content"><h2 id="reverse-engineer-binary-to-assembly">Reverse-engineer binary to assembly</h2> <pre><code>$00 $c3 $50 $01 $ce $ed $66 $66 $cc $0d $00 $0b $03 $73 $00 $83 $00 $0c $00 $0d $00 $08 $11 $1f $88 $89 $00 $0e $dc $cc $6e $e6 $dd $dd $d9 $99 $bb $bb $67 $63 $6e $0e $ec $cc $dd $dc $99 $9f $bb $b9 $33 $3e $45 $58 $41 $4d $50 $4c $45 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $00 $01 $33 </code></pre></section> </div> <div class="slide hidden" id="slide-10"> <section class="slide-content"><h2 id="make-program-that-emits-bytes">Make program that emits bytes</h2> <pre><code>$00 c, $c3 c, $50 c, $01 c, $ce c, $ed c, $66 c, $66 c, $cc c, $0d c, $00 c, $0b c, $03 c, $73 c, $00 c, $83 c, $00 c, $0c c, $00 c, $0d c, $00 c, $08 c, $11 c, $1f c, $88 c, $89 c, $00 c, $0e c, $dc c, $cc c, $6e c, $e6 c, $dd c, $dd c, $d9 c, $99 c, $bb c, $bb c, $67 c, $63 c, $6e c, $0e c, $ec c, $cc c, $dd c, $dc c, $99 c, $9f c, $bb c, $b9 c, $33 c, $3e c, $45 c, $58 c, $41 c, $4d c, $50 c, $4c c, $45 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $01 c, $33 c, </code></pre></section> </div> <div class="slide hidden" id="slide-11"> <section class="slide-content"><h2 id="find-the-patterns-and-meaning">Find the patterns and meaning</h2> <pre> $00 c, $c3 c, $50 c, $01 c, <span style="color: #AA00AA">$ce c, $ed c, $66 c, $66 c, $cc c, $0d c, $00 c, $0b c, $03 c, $73 c, $00 c, $83 c, $00 c, $0c c, $00 c, $0d c, $00 c, $08 c, $11 c, $1f c, $88 c, $89 c, $00 c, $0e c, $dc c, $cc c, $6e c, $e6 c, $dd c, $dd c, $d9 c, $99 c, $bb c, $bb c, $67 c, $63 c, $6e c, $0e c, $ec c, $cc c, $dd c, $dc c, $99 c, $9f c, $bb c, $b9 c, $33 c, $3e c,</span> <span style="color: #00AA00">$45 c, $58 c, $41 c, $4d c, $50 c, $4c c, $45 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c,</span> $00 c, $00 c, $01 c, $33 c, </pre></section> </div> <div class="slide hidden" id="slide-12"> <section class="slide-content"><h2 id="extract-patterns-into-definitions">Extract patterns into definitions</h2> <pre> <span style="color: #AA00AA">: logo $ce c, $ed c, $66 c, $66 c, $cc c, $0d c, $00 c, $0b c, $03 c, $73 c, $00 c, $83 c, $00 c, $0c c, $00 c, $0d c, $00 c, $08 c, $11 c, $1f c, $88 c, $89 c, $00 c, $0e c, $dc c, $cc c, $6e c, $e6 c, $dd c, $dd c, $d9 c, $99 c, $bb c, $bb c, $67 c, $63 c, $6e c, $0e c, $ec c, $cc c, $dd c, $dc c, $99 c, $9f c, $bb c, $b9 c, $33 c, $3e c, ;</span> <span style="color: #00AA00">: title $45 c, $58 c, $41 c, $4d c, $50 c, $4c c, $45 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, $00 c, ;</span> $00 c, $c3 c, $50 c, $01 c, <span style="color: #AA00AA">logo</span> <span style="color: #00AA00">title</span> $00 c, $00 c, $01 c, $33 c, </pre></section> </div> <div class="slide hidden" id="slide-13"> <section class="slide-content"><h2 id="implement-assembler">Implement assembler</h2> <p><img src="asm.png" alt="asm"></p> </section> </div> <div class="slide hidden" id="slide-14"> <section class="slide-content"><h2 id="full-forth-assembler-game">Full Forth Assembler "game"</h2> <pre> <span style="color: #00AA00">title: EXAMPLE</span> $150 ==> main: di, $ffff # sp ld, %11100100 # a ld, a [rGBP] ld, 0 # a ld, a [rSCX] ld, a [rSCY] ld, <span style="color: #777777">( ... )</span> </pre></section> </div> <div class="slide hidden" id="slide-15"> <section class="slide-content"><p><img src="helloworld.png" alt="helloworld"></p> </section> </div> <div class="slide hidden" id="slide-16"> <section class="slide-content"><p><img src="helloreaktor.jpg" alt="helloreaktor"></p> </section> </div> <div class="slide hidden" id="slide-17"> <section class="slide-content"><h1 id="now-what-">Now what? 🤔</h1> <h2 id="implementing-forth">Implementing Forth</h2> <ul> <li>Break binary compatibility</li> <li>New testing strategy<ul> <li>Unit tests</li> <li>Visual comparison</li> <li>Using emulator for automated testing</li> </ul> </li> <li>Rewriting <em>Hello World</em> to Forth</li> </ul> </section> </div> <div class="slide hidden" id="slide-18"> <section class="slide-content"><h3 id="implementing-forth">Implementing Forth</h3> <ul> <li>Add a compiler</li> <li>Implement code primitives</li> <li>Adding libraries</li> <li>Replacing ASM with Forth</li> </ul> </section> </div> <div class="slide hidden" id="slide-19"> <section class="slide-content"><p><img src="forth.png" alt="forth"></p> </section> </div> <div class="slide hidden" id="slide-20"> <section class="slide-content"><h1 id="the-final-test-">The final test 💪</h1> <h2 id="compiling-a-third-party-forth-game-">Compiling a third party Forth game...</h2> <h2 id=""> </h2> <pre><code class="lang-fs">\ sokoban - a maze game <span class="hljs-keyword">in</span> FORTH \ Copyright (C) <span class="hljs-number">1995</span>,<span class="hljs-number">1997</span>,<span class="hljs-number">1998</span>,<span class="hljs-number">2003</span>,<span class="hljs-number">2007</span>,<span class="hljs-number">2012</span>,<span class="hljs-number">2013</span>,<span class="hljs-number">2015</span> \ Free Software Foundation, Inc. \ This file is part <span class="hljs-keyword">of</span> Gforth. <span class="hljs-number">40</span> Constant /maze \ maximal maze line Create maze <span class="hljs-number">1</span> cells allot /maze <span class="hljs-number">25</span> * allot \ current maze Variable mazes <span class="hljs-number">0</span> mazes ! \ root pointer Variable soko <span class="hljs-number">0</span> soko ! \ player position Variable >maze <span class="hljs-number">0</span> >maze ! \ current compiled maze : maze-field ( -- addr n ) maze dup cell+ swap @ chars ; : .score ( -- ) .<span class="hljs-string">" Level: "</span> level# @ <span class="hljs-number">2</span> .r .<span class="hljs-string">" Score: "</span> score @ <span class="hljs-number">4</span> .r .<span class="hljs-string">" Moves: "</span> moves @ <span class="hljs-number">6</span> .r .<span class="hljs-string">" Rocks: "</span> rocks @ <span class="hljs-number">2</span> .r ; : .maze ( -- ) \ display maze <span class="hljs-number">0</span> <span class="hljs-number">0</span> at-xy .score cr maze-field over + swap DO I /maze <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">cr</span> /<span class="hljs-title">maze</span> <span class="hljs-title">chars</span> +<span class="hljs-title">LOOP</span> ;</span> </code></pre> </section> </div> <div class="slide hidden" id="slide-21"> <section class="slide-content"><p><img src="tweet1.png" alt="tweet1"></p> </section> </div> <div class="slide hidden" id="slide-22"> <section class="slide-content"><p><img src="tweet2.png" alt="tweet2"></p> </section> </div> <div class="slide hidden" id="slide-23"> <section class="slide-content"><h3 id="future-development-">Future development 🚀</h3> <ul> <li>ASM bug fixes</li> <li>Compiler optimisations</li> <li>GB Color support</li> <li>Declaritive RAM initialisation</li> <li>Automatic ROM bank switching</li> <li>Debugging tools</li> <li><strong>Actually writing a game</strong></li> <li>...</li> </ul> </section> </div> <div class="slide hidden dark" id="slide-24"> <section class="slide-content"><h1 id="-more-">🤓 More?</h1> <ul> <li><a href="https://ams-hackers.github.io/gbforth">ams-hackers/gbforth</a></li> <li><a href="https://www.youtube.com/watch?v=HyzD8pNlpwI">The Ultimate Game Boy Talk (33c3)</a></li> <li>Join <strong>#amsterdam-hacking</strong></li> </ul> <p><img src="gbschematic2.jpg" alt="background"></p> </section> </div> <script type="text/javascript"> /** * Returns the current page number of the presentation. */ function currentPosition() { return parseInt(document.querySelector('.slide:not(.hidden)').id.slice(6)); } /** * Navigates forward n pages * If n is negative, we will navigate in reverse */ function navigate(n) { var position = currentPosition(); var numSlides = document.getElementsByClassName('slide').length; /* Positions are 1-indexed, so we need to add and subtract 1 */ var nextPosition = (position - 1 + n) % numSlides + 1; /* Normalize nextPosition in-case of a negative modulo result */ nextPosition = (nextPosition - 1 + numSlides) % numSlides + 1; document.getElementById('slide-' + position).classList.add('hidden'); document.getElementById('slide-' + nextPosition).classList.remove('hidden'); updateProgress(); updateURL(); updateTabIndex(); } /** * Updates the current URL to include a hashtag of the current page number. */ function updateURL() { try { window.history.replaceState({} , null, '#' + currentPosition()); } catch (e) { window.location.hash = currentPosition(); } } /** * Sets the progress indicator. */ function updateProgress() { var progressBar = document.querySelector('.progress-bar'); if (progressBar !== null) { var numSlides = document.getElementsByClassName('slide').length; var position = currentPosition() - 1; var percent = (numSlides === 1) ? 100 : 100 * position / (numSlides - 1); progressBar.style.width = percent.toString() + '%'; } } /** * Removes tabindex property from all links on the current slide, sets * tabindex = -1 for all links on other slides. Prevents slides from appearing * out of control. */ function updateTabIndex() { var allLinks = document.querySelectorAll('.slide a'); var position = currentPosition(); var currentPageLinks = document.getElementById('slide-' + position).querySelectorAll('a'); var i; for (i = 0; i < allLinks.length; i++) { allLinks[i].setAttribute('tabindex', -1); } for (i = 0; i < currentPageLinks.length; i++) { currentPageLinks[i].removeAttribute('tabindex'); } } /** * Determines whether or not we are currently in full screen mode */ function isFullScreen() { return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement; } /** * Toggle fullScreen mode on document element. * Works on chrome (>= 15), firefox (>= 9), ie (>= 11), opera(>= 12.1), safari (>= 5). */ function toggleFullScreen() { /* Convenient renames */ var docElem = document.documentElement; var doc = document; docElem.requestFullscreen = docElem.requestFullscreen || docElem.msRequestFullscreen || docElem.mozRequestFullScreen || docElem.webkitRequestFullscreen.bind(docElem, Element.ALLOW_KEYBOARD_INPUT); doc.exitFullscreen = doc.exitFullscreen || doc.msExitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen; isFullScreen() ? doc.exitFullscreen() : docElem.requestFullscreen(); } document.addEventListener('DOMContentLoaded', function () { // Update the tabindex to prevent weird slide transitioning updateTabIndex(); // If the location hash specifies a page number, go to it. var page = window.location.hash.slice(1); if (page) { navigate(parseInt(page) - 1); } document.onkeydown = function (e) { var kc = e.keyCode; // left, down, H, J, backspace, PgUp - BACK // up, right, K, L, space, PgDn - FORWARD // enter - FULLSCREEN if (kc === 37 || kc === 40 || kc === 8 || kc === 72 || kc === 74 || kc === 33) { navigate(-1); } else if (kc === 38 || kc === 39 || kc === 32 || kc === 75 || kc === 76 || kc === 34) { navigate(1); } else if (kc === 13) { toggleFullScreen(); } }; if (document.querySelector('.next') && document.querySelector('.prev')) { document.querySelector('.next').onclick = function (e) { e.preventDefault(); navigate(1); }; document.querySelector('.prev').onclick = function (e) { e.preventDefault(); navigate(-1); }; } }); </script> </body> </html>