📄 hacking
字号:
HACKING ON THE GNUBOY SOURCE TREE BASIC INFOIn preparation for the first release, I'm putting together a simpledocument to aid anyone interested in playing around with or improvingthe gnuboy source. First of all, before working on anything, youshould know my policies as maintainer. I'm happy to accept contributedcode, but there are a few guidelines:* Obviously, all code must be able to be distributed under the GNUGPL. This means that your terms of use for the code must be equivalentto or weaker than those of the GPL. Public domain and MIT-stylelicenses are perfectly fine for new code that doesn't incorporateexisting parts of gnuboy, e.g. libraries, but anything derived from orbuilt upon the GPL'd code can only be distributed under GPL. When indoubt, read COPYING.* Please stick to a coding and naming convention similar to theexisting code. I can reformat contributions if I need to whenintegrating them, but it makes it much easier if that's already doneby the coder. In particular, indentions are a single tab (char 9), andall symbols are all lowercase, except for macros which are alluppercase.* All code must be completely deterministic and consistent across allplatforms. this results in the two following rules...* No floating point code whatsoever. Use fixed point or better yetexact analytical integer methods as opposed to any approximation.* No threads. Emulation with threads is a poor approximation if donesloppily, and it's slow anyway even if done right since things must bekept synchronous. Also, threads are not portable. Just say no tothreads.* All non-portable code belongs in the sys/ or asm/ trees. #ifdefshould be avoided except for general conditionally-compiled code, asopposed to little special cases for one particular cpu or operatingsystem. (i.e. #ifdef USE_ASM is ok, #ifdef __i386__ is NOT!)* That goes for *nix code too. gnuboy is written in ANSI C, and I'mnot going to go adding K&R function declarations or #ifdef's to makesure the standard library is functional. If your system is THATbroken, fix the system, don't "fix" the emulator.* Please no feature-creep. If something can be done through anexternal utility or front-end, or through clever use of the rcsubsystem, don't add extra code to the main program.* On that note, the modules in the sys/ tree serve the singularpurpose of implementing calls necessary to get input and displaygraphics (and eventually sound). Unlike in poorly-designed emulators,they are not there to give every different target platform its own guiand different set of key bindings.* Furthermore, the main loop is not in the platform-specific code, andit will never be. Windows people, put your code that would normally goin a message loop in ev_refresh and/or sys_sleep!* Commented code is welcome but not required.* I prefer asm in AT&T syntax (the style used by *nix assemblers andlikewise DJGPP) as opposed to Intel/NASM/etc style. If you really mustuse a different style, I can convert it, but I don't want to add extradependencies on nonstandard assemblers to the build process. Also,portable C versions of all code should be available.* Have fun with it. If my demands stifle your creativity, feel free tofork your own projects. I can always adapt and merge code later ifyour rogue ideas are good enough. :)OK, enough of that. Now for the fun part... THE SOURCE TREE STRUCTURE[documentation]README - general information related to using gnuboyINSTALL - compiling and installation instructionsHACKING - this file, obviouslyCOPYING - the gnu gpl, grants freedom under condition of preseving it[build files]Version - doubles as a C and makefile include, identifies version numberRules - generic build rules to be included by makefilesMakefile.* - system-specific makefilesconfigure* - script for generating *nix makefiles[non-portable code]sys/*/* - hardware and software platform-specific codeasm/*/* - optimized asm versions of some code, not used yetasm/*/asm.h - header specifying which functions are replaced by asmasm/i386/asmnames.h - #defines to fix _ prefix brain damage on DOS/Windows[main emulator stuff]main.c - entry point, event handler...basically a messloader.c - handles file io for rom and ramemu.c - another mess, basically the frame loop that calls state.cdebug.c - currently just cpu trace, eventually interactive debugginghw.c - interrupt generation, gamepad state, dma, etc.mem.c - memory mapper, read and write operationsfastmem.h - short static functions that will inline for fast memory ioregs.h - macros for accessing hardware registerssave.c - savestate handling[cpu subsystem]cpu.c - main cpu emulationcpuregs.h - macros for cpu registers and flagscpucore.h - data tables for cpu emulationasm/i386/cpu.s - entire cpu core, rewritten in asm[graphics subsystem]fb.h - abstract framebuffer definition, extern from platform-specificslcd.c - main control of refresh procedurelcd.h - vram, palette, and internal structures for refreshasm/i386/lcd.s - asm versions of a few critical functionslcdc.c - lcdc phase transitioning[input subsystem]input.h - internal keycode definitions, etc.keytables.c - translations between key names and internal keycodesevents.c - event queue[resource/config subsystem]rc.h - structure defsrccmds.c - command parser/processorrcvars.c - variable exports and command to set rcvarsrckeys.c - keybindingds[misc code]path.c - path searchingsplit.c - general purpose code to split strings into argv-style arrays OVERVIEW OF PROGRAM FLOWThe initial entry point main() main.c, which will process the commandline, call the system/video initialization routines, load therom/sram, and pass control to the main loop in emu.c. Note that thesystem-specific main() hook has been removed since it is not needed.There have been significant changes to gnuboy's main loop since theoriginal 0.8.0 release. The former state.c is no more, and the newcode that takes its place, in lcdc.c, is now called from the cpu loop,which although slightly unfortunate for performance reasons, isnecessary to handle some strange special cases.Still, unlike some emulators, gnuboy's main loop is not the cpuemulation loop. Instead, a main loop in emu.c which handles videorefresh, polling events, sleeping between frames, etc. callscpu_emulate passing it an idea number of cycles to run. The actualnumber of cycles for which the cpu runs will vary slightly dependingon the length of the final instruction processed, but it should neverbe more than 8 or 9 beyond the ideal cycle count passed, and theactual number will be returned to the calling function in case itneeds this information. The cpu code now takes care of all timer andlcdc events in its main loop, so the caller no longer needs to beaware of such things.Note that all cycle counts are measured in CGB double speed MACHINEcycles (2**21 Hz), NOT hardware clock cycles (2**23 Hz). This isnecessary because the cpu speed can be switched between single anddouble speed during a single call to cpu_emulate. When running insingle speed or DMG mode, all instruction lengths are doubled.As for the LCDC state, things are much simpler now. No more hugeglorious state table, no more P/Q/R, just a couple simple functions.Aside from the number of cycles left before the next state change, allthe state information fits nicely in the locations the Game Boy itselfprovides for it -- the LCDC, STAT, and LY registers.If the special cases for the last line of VBLANK look strange to you,good. There's some weird stuff going on here. According to documentsI've found, LY changes from 153 to 0 early in the last line, thenremains at 0 until the end of the first visible scanline. I don'trecall finding any roms that rely on this behavior, but I implementedit anyway.That covers the basics. As for flow of execution, here's a simplifiedcall tree that covers most of the significant function calls takingplace in normal operation: main sys/ \_ real_main main.c |_ sys_init sys/ |_ vid_init sys/ |_ loader_init loader.c |_ emu_reset emu.c \_ emu_run emu.c |_ cpu_emulate cpu.c | |_ div_advance cpu.c * | |_ timer_advance cpu.c * | |_ lcdc_advance cpu.c * | | \_ lcdc_trans lcdc.c | | |_ lcd_refreshline lcd.c | | |_ stat_change lcdc.c | | | \_ lcd_begin lcd.c | | \_ stat_trigger lcdc.c | \_ sound_advance cpu.c * |_ vid_end sys/ |_ sys_elapsed sys/ |_ sys_sleep sys/ |_ vid_begin sys/ \_ doevents main.c (* included in cpu.c so they can inline; also in cpu.s) MEMORY READ/WRITE MAPWhenever possible, gnuboy avoids emulating memory reads and writeswith a function call. To this end, two pointer tables are kept -- onefor reading, the other for writing. They are indexed by bits 12-15 ofthe address in Game Boy memory space, and yield a base pointer fromwhich the whole address can be used as an offset to access Game Boymemory with no function calls whatsoever. For regions that cannot beaccessed without function calls, the pointer in the table is NULL.For example, reading from address addr can be accomplished by testingto make sure mbc.rmap[addr>>12] is not NULL, then simply readingmbc.rmap[addr>>12][addr].And for the disbelievers in this optimization, here are some numbersto compare. First, FFL2 with memory tables disabled: % cumulative self self total time seconds seconds calls us/call us/call name 28.69 0.57 0.57 refresh_2 13.17 0.84 0.26 4307863 0.06 0.06 mem_read 11.63 1.07 0.23 cpu_emulateNow, with memory tables enabled: 38.86 0.66 0.66 refresh_2
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -