{"id":13618314,"url":"https://github.com/travisgoodspeed/gbrom-tutorial","last_synced_at":"2026-01-27T11:02:06.335Z","repository":{"id":144627225,"uuid":"616638268","full_name":"travisgoodspeed/gbrom-tutorial","owner":"travisgoodspeed","description":"Tutorial for extracting the GameBoy ROM from photographs of the die.","archived":false,"fork":false,"pushed_at":"2025-06-01T23:19:43.000Z","size":81369,"stargazers_count":1143,"open_issues_count":0,"forks_count":32,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-06-02T09:16:19.454Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/travisgoodspeed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-20T19:29:25.000Z","updated_at":"2025-06-01T23:19:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"dc7b9d02-1984-46c4-b479-d66ac846c5b3","html_url":"https://github.com/travisgoodspeed/gbrom-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/travisgoodspeed/gbrom-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisgoodspeed%2Fgbrom-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisgoodspeed%2Fgbrom-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisgoodspeed%2Fgbrom-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisgoodspeed%2Fgbrom-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/travisgoodspeed","download_url":"https://codeload.github.com/travisgoodspeed/gbrom-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisgoodspeed%2Fgbrom-tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28812367,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T07:41:26.337Z","status":"ssl_error","status_checked_at":"2026-01-27T07:41:08.776Z","response_time":168,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T20:01:58.459Z","updated_at":"2026-01-27T11:02:06.329Z","avatar_url":"https://github.com/travisgoodspeed.png","language":null,"funding_links":[],"categories":["硬件_其他","Others"],"sub_categories":["网络服务_其他"],"readme":"# GameBoy ROM Tutorial\n\nHowdy y'all,\n\nThis is a quick little tutorial on mask ROM recovery, in which we'll\nbegin with photographs of the mask ROM from Nintendo's GameBoy and end\nup with a ROM file that can be disassembled or emulated.\n\nThe GameBoy is a good target for this because it uses what's called a\nVia ROM, meaning that metal Vias between layers encode the individual\nbits and those bits can be read from the surface of the chip.  The ROM\nis also small enough that I can include it in a Github repository and\nthat you won't spend weeks working out minor bit errors.\n\nCheers from Knoxville,\n\n--Travis Goodspeed\n\n## Photography\n\nWe'll begin with `dmg01cpurom.bmp`, which I photographed in my home\nlab after decapsulating a chip in HNO3 and cleaning it in an\nultrasonic bath of acetone.  This chip does not require delayering\nwith hydrofluoric acid or bit staining with a Dash Etch solution of\nHNO3, HF, and acetic acid.\n\nThe photo was produced in my metallurgical microscope as twenty-two\nframes under 50x magnification, which were then stitched together in\nHugin.  A biological microscope will not work, because the silicon\nsubstrate is opaqe to visible light; in a metallurgical microscope,\nthe light reflects off of the sample rather than transmitting through\nit.\n\nMy original photo was too large for Github, so the version that you\nwill work with is reduced in resolution.  Lossy compression is avoided\nbecause it can confuse the image recognition.\n\nIf you produce your own photo of the DMG-01-CPU chip, the instructions\nhere will work just as well.\n\n## Bit Extraction\n\nIn this tutorial, we'll be extracting the bits with\n[MaskRomTool](https://github.com/travisgoodspeed/maskromtool/), a CAD\nprogram of mine for rapid bit extraction.\n\nBegin by compiling MaskRomTool and installing it.  If you are\ncomfortable with the unix command-line, you will want the executables\n`maskromtool` and `gatorom` to be in your `$PATH`.  If not, don't\nworry about that part, as all the features are also available from the\nGUI.\n\nOn Windows and macOS, you can avoid compiling the code by using a\n[prebuilt\nrelease](https://github.com/travisgoodspeed/maskromtool/releases).\nThese are compiled every few months when the code feels particularly\nstable.\n\nAfter you have it running, use MaskRomTool to open `dmg01cpurom.bmp`.\n\n![First Opening of the Image](screenshots/10-firstopen.png)\n\nNotice how a grey crosshair follows your mouse.  They begin straight,\nbut will soon tilt to match the row and column lines that you place on\nthe ROM.  This allows you to predict where repeated rows and columns of the same\nangle will fall, as most images are tilted but most bits are in\nperfect lines.\n\nNotice also that the bits in the ROM come in pairs of regular rows and\ngroups of eight regular columns.  In MaskRomTool, you'll be placing\nrows and columns, but there are no strict limits about matching the\ngrouping of the original image.  If you find that you can reliably\nplace a massive row that crosses the entire width of the image, or a\nmassive column that crosses the entire height of the image, feel free\nto do so.  If the image is crooked and you need a very short line to\nwork around an error, that's also fine.\n\n### Navigation\n\nTo move around the image, you will use either your mouse or your\nlaptop's trackpad.  Mice are preferred because right clicking is a\nlittle easier.\n\nDrag the background with the middle mouse button to scroll a little\nbit, or use two fingers on the trackpad to pan around the screen.  If\nyou hold the shift key, this panning will be much faster.\n\nZoom is very important for these projects.  The `QAZ` keys handle\nzooming, with `Q` snapping to native resolution, `A` and `Z` zooming\nin and out.  On a mouse, holding `ctrl` and spinning the scroll wheel\nwill also adjust the zoom.  With a trackpad, you might pinch to zoom,\nbut only if your operating system supports it.\n\nSpend a moment moving around the ROM photograph, guessing at what\ndifferent structures are.  After that, we'll start marking up the\nimage.\n\n### Placing Rows\n\nWe'll begin with a few short rows and try long ones later.  First,\nclick a little to the left of the leftmost bit, and then move your\nmouse to the right but do **NOT** click.  Instead press the `R` key to\nplace a Row.  A thin black line will appear between those two points.\n\nYou could repeat this to place all of the rows, but that would be\nlabor intensive and might involve a lot of scrolling for very long\nones.  Instead, keep your mouse on the right side but move it down a\nlittle.  When your crosshair lines up with the row, press the spacebar\nto drop another row with the same length and angle.\n\nRepeating this across many rows should mark them out in short order.\nIn the following screenshot, I've marked short segments of the first\neight rows.\n\nOnce you get the hang of marking repeated rows from only their\nright-hand side, you might as well mark them across the entire image\nto save time and effort.  If straight lines can't cross the entire\nimage, then mark shorter lengths or better align your panorama of\nphotographs.\n\nIf you place a row in the wrong position, there are ways to move it.\nFirst drag a box with the left mouse button to select one or more\nlines, then drag with the right mouse button to move them.  The arrow\nkeys will also move a selection, and the `S` key will set the position\nof a single line to a new end point.\n\n![Eight short rows marked in the ROM.](screenshots/20-firstrows.png)\n\n### Placing Columns\n\nWhile you've marked some rows, the software still doesn't know\nwhere your bits are because you haven't yet marked any columns.\n\nTo mark you first column, first click above the bit in your first\ncolumn.  Then, as with a row, move your mouse beneath the last bit of\nthe column and hit `C` to place a column line.  (Do **NOT** click\na second time.)\n\nRepeat this by moving your mouse beneath the other last bits and\npressing the spacebar.  As each column line is dropped across the row\nlines, blue squares will appear over each bit.  The software now knows\nwhere the bits are, and in the next step we'll teach it to know the\ndifference between a one and a zero.\n\nPlease double-check that bits are in reasonable positions.  If any of\nthe bit boxes are not directly over the bit, select the offending row\nor column and drag with the right mouse button to correct its\nposition.  If the angle is wrong, use `D` to erase the line and then\ndraw a fresh one.\n\n![First bits are now recognized.](screenshots/30-firstbits.png)\n\n### Recognizing Bits\n\nTo identify bits, the software needs not just the bit positions that\nyou've provided, but also a threshold and color channel to distinguish\nthe bits.  Click Edit and then Choose Bit Threshold to see the\nfollowing graph and choose your own threshold.\n\n![Histogram of the first 64 bits.](screenshots/40-histogram.png)\n\nThis histogram of the first 64 bits shows gaps in all three color\nchannels.  The largest gap is in the Green channel, so I would set the\nthreshold to 172 in that channel.  Notice how the bit boxes\nautomatically adjust themselves as you change the threshold.\n\nAlthough the Blue and Red channels could work for this image, we want\nthe Green channel because it has the *largest* gap.  When we start\nadding up all of the bits in the image, the larger color distances\nwill ensure that we get fewer bit errors.\n\n![Another histogram of the first 64 bits.](screenshots/50-histogram2.png)\n\nOnce you've set the first sixty-four bits, click View and ASCII\nPreview to see them.  Ain't that nifty?\n\n```\n11101011\n01111111\n00110111\n01101111\n10110001\n01101110\n01011100\n10110101\n```\n\n### More Bits\n\nNow that you've got a taste of recognizing sixty four bits, let's\nrecognize all of them.\n\nFirst, wipe our your previous work by either deleting\n`dmg01cpurom.bmp.json` and opening `dmg01cpurom.bmp` in a fresh\ninstance of Mask ROM Tool or bulk erase your lines.  Bulk erasure is\nperformed by drag-selecting lines and then pressing `D` to cut them\nout.\n\n(Strictly speaking, you could also mark the image with many very small\nrows and columns.  The only reason I recommend against that is that\nit takes forever.)\n\nAfter erasing the lines, drop a row that goes from the far left of the\nscreen to the far right.  If you are starting over instead of deleting\nprior work, it sometimes helps to first draw a shorter row so that the\nangle of the cross-hairs will be tilted the same as the image.  You\ncan draw an attempt at a row with `R` and then delete it with `D`,\nwithout the software forgetting about your first starting position.\n\nAfter getting the first line drawn, move your mouse down the right\nside of the photograph, hitting the space bar whenever you\npass a row to drop a line.  If you find that you marked the bit just a\nlittle off, you can use the `S` key to Set the position of the last\nline, moving it to the new mouse location.  Arrow keys and dragging\nwith a right click will also reposition it, with bit values changing\nalong with the positions.\n\nIf the rows are particularly regular, you might place several at time.\nTo do this, select some with the left mouse button and then press\n`Shift+D` to duplicate the collection.  The lines can then be moved to\nthe new position with the right click drag, while a duplicate copy\nwill remain at the original position.\n\nAfter drawing the long rows, draw the columns.  Just click once above\nthe first bit to set a start point, then hit `C` beneath the last bit\nto draw a column.  The spacebar will drop a new column of the same angle\nat another end point, so you can step through the image and draw all\nof the columns in short order.\n\n![All bits have been marked.](screenshots/60-allbits.png)\n\nWhen all the bits have been set, use Edit / Choose Bit Threshold to\nadjust the threshold a bit.  Notice how the curve is a lot smoother\nwith so many more bits, and that 151 might be a better choice than\n172.\n\n![Smoother histogram.](screenshots/61-newhistogram.png)\n\n### Finding and Fixing Mistakes\n\nIt's a great feeling to have all the bits marked, but that's rarely\nthe end of it.  Before continuing on to decoding the ROM, it's a good\nidea to do some quick sanity checks and make sure that no mistakes\nwere made.\n\nThe `V` key or DRC / Evaluate Rules to run some quick sanity checks\nover your design.  For example, what if you placed a line wrong and\nthe color of a bit was suspiciously close to the threshold?  You might\nget a DRC error like this one, which you could resolve by correcting\nthe line placement.  Each Design Rule Check (DRC) violation has a\nposition and appears as a yellow box in the GUI.  The `E` key will\njump to the next error position in the list.\n\n![A poorly placed column made a bit\nambigious.](screenshots/70-ambiguous.png)\n\nWhile mistakes like a misplaced line are your fault, those mistakes\nwhich are not your fault must also be corrected.  Sometimes a bit of\ndust sneaks into the photograph, obscuring a bit's value at the sample\nlocation.  Sometimes a chemistry error makes the bit a little hard to\nsee.\n\nIn situations like this, where you know better than the machine, press\n`Shift+F` with your mouse over the bit to Force its value and place a\nlittle green box around the bit.  If the wrong value is applied,\npressing `Shift+F` again will flip it.\n\n![Forcing a bit to its proper value.](screenshots/71-force.png)\n\nAfter all of this, the bits are finally complete!\n\n```\n11101011111100101100101100110010011000110111110000100001011100101110000000110011110001001001001011000100001011110001101100001000\n01111111011100110111111101100011001010110100111100000110101010110011001111110011001010111011000000111000001011101101011011101111\n00110111110110110111011111010111011001101001011101111111110110100111000101110010010110000101011101110001111101110111101111011000\n01101111111000111110111011110110001011100101011010100010001110000111100000111010011111000111001100101111010000110100111111101001\n10110001001100001011100110110111000110011101100111100000111100111111011010110001111111100111011010111010000110100100001100010011\n01101110011100110110100100110111011100110101101001001111111100110010111110100011001110011010011101111010000111100111001010110010\n01011100011101111111110001110111101100000101101100111000011100010011000011110101001100001011111000110000100110010111011111010010\n10110101110101111011101001011111001110101111101000010101111100011101011111000011111010111010101100001110011110011011011111000101\n00011111000101000001101110011110101111001111000011111011011100000100011011010000110100111001100100110011110101000101101110110110\n11011001000101100111100100011011001110010101010100001101111101110110010110000111010101101101111010101100101100101101111110010111\n11111111111110011111101101010101001101111101000010100110101010011011010011111001101101001101010110101010010101011110010110100011\n01011110111110100001111011010110001000011101000000111011001011100101001111101110100110110000101000101011100001000001111000100001\n11011000100010111011110010010101001011000111000010011000111110001111011011000000100111001101010010001100111101100010100110111001\n11111011001010011111101000111101001100101111100110110101011111011011110110000101001011001101000100110111001101011110110110001010\n00111111111101100011101111110010001011001111010000011111101111010011011111110111001101110111010101011111111110110101011100111111\n10101101111101111000110111110110100001010111100111001101101110100100111111000011011110101010001101011100100011111100111110011111\n```\n\n## About the CLI\n\nSo far, we've been using this tool in the GUI, but when you get to\nlarger projects you'll want to automate some things with the CLI.  By\ndefault it launches the GUI, so you'll want to add `-platform\noffscreen -e` when running it standalone, to both avoid opening\nwindows and to exit on completion.\n\n```\nUsage: maskromtool [options] image json\nMask ROM Tool\n\nOptions:\n  -h, --help                 Displays help on commandline options.\n  --help-all                 Displays help including Qt specific options.\n  -v, --version              Displays version information.\n  -V, --verbose              Print verbose debugging messages.\n  --stress                   Stress test bit marking.\n  -e, --exit                 Exit after processing arguments.\n  --disable-opengl           Disable OpenGL.\n  --enable-opengl            Enable OpenGL.\n  -d, --drc                  Run default Design Rule Checks.\n  -D, --DRC                  Run all Design Rule Checks.\n  --sampler \u003cDefault\u003e        Bit Sampling Algorithm.\n  --diff-ascii \u003cfile\u003e        Compares against ASCII art, for finding errors.\n  -a, --export-ascii \u003cfile\u003e  Export ASCII bits.\n  -o, --export \u003cfile\u003e        Export ROM bytes.\n  --export-histogram \u003cfile\u003e  Export histogram.\n  --export-csv \u003cfile\u003e        Export CSV bits for use in Matlab or Excel.\n  --export-json \u003cfile\u003e       Export JSON bit positions.\n  --export-python \u003cfile\u003e     Export Python arrays.\n  --export-photo \u003cfile\u003e      Export a photograph.\n\nArguments:\n  image                      ROM photograph to open.\n  json                       JSON lines to open.\n```\n\nSupposing that we have the ROM as `dmg01cpurom.bmp` and its matching\n`.json` file, we can export the bits from the CLI like this in macOS\nor Linux.  In Windows, we call `maskromtoolcli.exe` instead of\n`maskromtool` so that the console connection is maintained.\n\n```\ndell% maskromtool -platform offscreen -e dmg01cpurom.bmp -a DMG_ROM.txt\nAllocation limit was  128 MB\nDisabling allocation limit.\nLoaded background image of  9000 , 2249\nDone loading, now marking bits.\nExporting to ASCII.\ndell% \n```\n\n\n## Decoding a ROM File\n\nYou now have the bits of your project in physical order, but this is\nquite different from the logically ordered bytes that a disassembler\nor emulator would prefer.  We'll first do this graphically, and then\nalso see how to do it from the Unix command line.\n\nPrevious versions of this guide suggested installing MAME's `unidasm`\ndisassembler or [Radare2](https://rada.re).  Those still work, but\n[GoodASM](https://github.com/travisgoodspeed/goodasm) is now included\nin MaskRomTool and works just fine for the GameBoy's SM83\narchitecture.\n\n### Decoding Graphically\n\nYou can play around with decoding settings manually with\nEdit/Decoding.  Begin by setting the disassembly architecture to\n`goodasm/sm83` and the wordsize to `8`.  The flips, rotation, and banking\nwill be solved for you in a bit, and the list of flags at the bottom\nshows you the options that would be passed to\n[GatoROM](https://github.com/travisgoodspeed/maskromtool/blob/master/GATOREADME.md)\nfor decoding on the command line.\n\n![Graphical solution](screenshots/decoder.png)\n\nYou can solve for particular byte sequences or Yara rules with\nView/Solver.  Double-clicking a solution will reconfigure the decoder\nand update the hex and disassembly views, so that they can be quickly\nsearched.\n\nBegin by using the Bytes tap of the Solver to solve for `0:31`, which\nmeans that the first three bytes will be `31`.  This is the SM83\nmachine code for setting the stack pointer, and it results in three\npotential decodings.  Double clicking on a solution will apply those\nsettings to the decoder, updating the other views to match.\n\n![Graphical solution](screenshots/solver.png)\n\nView/HexPreview will show the decoding live in hexadecimal.  After\nselecting some bytes, you can also highlight them with\nView/HighlightHexSelection to see where those bits are located in your\nproject file.\n\n![Screenshot of highlighted bytes in the GameBoy view.](screenshots/hexview.png)\n\n\nView/Disassembly will show the disassembled code, using the built-in\nGoodASM disassembler or external calls to Radare2 or Unidasm.  Jumping\nbetween the three solutions you have in your Solutions window, you'll\nfind that `--decode-cols-downr -i -r 0 --flipx` decodes the first\ninstruction to `ld sp, #0xfffe`.  That sets the stack pointer to the\ntop of internal RAM, indicating that it's the correct decoding.\n\n![Screenshot of disassembly.](screenshots/goodasm.png)\n\n\n### Decoding with GatoROM\n\n[GatoROM](https://github.com/travisgoodspeed/maskromtool/blob/master/GATOREADME.md)\nis a bit decoder that ships with MaskRomTool.  It runs from the\ncommand line, but under the hood it is powered by the same libraries\nthat run in the GUI solver.\n\nFirst we need an ASCII file of the ROM bits.  You can generate this\nwith File / Export / ASCII in MaskRomTool's GUI or from the CLI with\n`maskromtool -platform offscreen dmg01cpurom.bmp -a DMG_ROM.txt -e`.\n\nHere's how to solve for the ROM knowing that the first two bytes are\n`31` and `fe`.  The `-z` flag tells it that we want\n[Zorrom](https://github.com/JohnDMcMaster/zorrom) compatibility mode,\nand it accurately identifies three potential decodings before writing\nthe correct one to `DMG_ROM.bin`.\n\n```\ndell% gatorom DMG_ROM.txt --solve --solve-bytes \"0:31,1:fe\" -z -o DMG_ROM.bin\nGrade 50        31 11 47 fe 3e f9 1e 0e         -z --decode-cols-left -i -r 180 --flipx \nGrade 50        8a fe a8 01 d4 52 b0 a4         -z --decode-squeeze-lr -r 180 --flipx \nGrade 100       31 fe ff af 21 ff 9f 32         -z --decode-cols-downr -i -r 180 --flipx \nExporting       -z --decode-cols-downr -i -r 180 --flipx \ndell% \n```\n\nWe can also search in other ways.  What if we know that `31 fe ff`\nexists somewhere in the image, but we don't know exactly where?  This\nyields three potential solutions, and we can explore the different\nones if needed by dumping them to files.\n\n```\ndell% gatorom DMG_ROM.txt --solve --solve-string \"31,fe,ff\" \nGrade 100       f5 06 19 78 86 23 05 20         --decode-cols-downl -i -r 0 --flipx \nGrade 100       31 fe ff af 21 ff 9f 32         --decode-cols-downr -i -r 0 --flipx \nGrade 100       f5 06 19 78 86 23 05 20         --decode-cols-downl-swap -i -r 0 --flipx \ndell% \n```\n\nWe can also have `gatorom` ask `goodasm` whether the assembly is valid\nin a particular language.  Not all languages support this, but the\nSM83 module can get a very accurate guess by looking for the Nintendo\nlogo that exists in the GameBoy's ROM and in every GameBoy cartridge.\n\n```\nair% gatorom --solve --solve-goodasm sm83 bits.txt -o decoded.bin\n100   \t31 fe ff af 21 ff 9f 32 \t-cols-downr-i-r0-flipx.bin\nExporting\t--decode-cols-downr -i -r 0 --flipx \nair%\n```\n\n### Loading the hex bytes into radare2\n\nIn order to view the disassembly of the hex representation of the\nextracted bits in [radare2](http://github.com/radareorg/radare2), you\ncan use the following:\n\n```\n; r2 malloc://256               # open radare2 with 256 bytes of memory available\n[0x00000000]\u003e e asm.arch=gb     # set the gameboy architecture\n[0x00000000]\u003e e asm.bits=8      # set the bits to 8\n[0x00000000]\u003e wxf hex.txt       # load the hex file into memory starting at 0x0\n[0x00000000]\u003e pD 0xff           # print 0xff bytes of disassembly\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisgoodspeed%2Fgbrom-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftravisgoodspeed%2Fgbrom-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisgoodspeed%2Fgbrom-tutorial/lists"}