;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This file installs a very complex custom GFX routine ;; to SMAS SMB1. Ths file aims at accomplishing several ;; goals: ;; ;; 1) Custom GFX per level ;; SMAS SMB1 uses a system to insert GFX which is designed ;; to save space. This proves inefficient to the modern hacker, ;; so of course, making a better GFX loading system is a must. ;; ;; 2) Allowing up to 0x1000 GFX files ;; If GFX per level routine is to be created, it is obvious that ;; there should be more GFX to choose from. Following Lunar Magic's ;; design, the limit will be set at 0x1000 which is more than reasonable. ;; ;; 3) LZ2 Compression ;; Having 0x1000 GFX files exist uncompressed is a very bad idea! ;; LZ2 Compression is a popular algorithm used by SMW, Yoshi's Island, ;; Zelda III, and other great SNES games. SMAS does not use this, so ;; it would be smart to implement it. ;; ;; 4) Fix several problems caused by (1) through (3) ;; Many issues have arisen due to installing all the previous ASM hacks. ;; Most notable are any cutscene GFX (game over, time up, prelevel scene, ;; save princess peach scene, etc.) So we need to fix all of that. ;; ;; That is indeed a lot to cover and the length and true complexual ;; nature of the GFX.asm file (and it's sibling branches) will show ;; that. ;; ;; You can modify this, but be careful. A lot of what goes on in this ;; file is deeply integrated to other parts of the game. ;; ;; Code ©2009-2010 spel werdz rite (SWR) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Definitions of stuff ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;; !AnimGFX1 = $7F4000 ;\ !AnimGFX2 = $7F5000 ;|RAM address locations of where !AnimGFX3 = $7F6000 ;|the animated GFX will be stored. !AnimGFX4 = $7F7000 ;/ !MainGFX = $7F8000 ;This serves as a GFX "template" when doing VRAM transfers !PlayerGFX1 = $7F8000 ;Page 1 of the current player's GFX !PlayerGFX2 = $7F9000 ;Page 2 of the current player's GFX !MiscGFX = $7FA000 ;Holds some extra GFX data for random cutscenes !PrincessGFX = $7FB000 ;GFX data of the "Rescue Princess" cutscene ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This clears some old data and makes a ;; few modications to certain bytes, as ;; well as jump to new routines when needed. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;GFX Initialization Routine ORG $03812B REP $59 : NOP ORG $03812B JSL InitGFX ORG $049266 JSL LoadCutsceneGFX ORG $049313 ;JSL LoadNextLevelGFX ORG $049655 REP $03 : NOP ;Changes which address to load the Player GFX from. ORG $04D834 dw !PlayerGFX1 ORG $04D847 REP $0D : NOP LDA #$7F ORG $04DD87 db $7F ORG $04DD9C db $B0 ORG $04ED2E REP $2C : NOP ORG $04ED2E RTL ;Changes addresses of animation frame GFX files. ORG $05E64C db $7F ORG $05E654 db $00 ORG $05E687 db $7F ORG $05E699 db $AB ORG $05E6AF REP $01CA : NOP ORG $05E6B1 JML LoadLevelGFX ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; These are some fundamental data files ;; which insert the LZ2 data and get their pointers. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; incsrc SMB1/GFX/Files.asm incsrc SMB1/GFX/Tables.asm incsrc SMB1/GFX/Files_Locs.asm incsrc SMB1/GFX/Files_Ptrs.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This is the actual start of the GFX ASM hack. ;; It's a pretty big mess, but I've tried my best ;; to keep it legible. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ORG !GFX incsrc SMB1/GFX/LZ2.asm DoDecompress: LDA.l GFX_Lo,x STA.b !LZ2_Lo LDA.l GFX_Hi,x STA.b !LZ2_Hi LDA.l GFX_Bk,x STA.b !LZ2_Bk JMP DecompressGFXPage ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This is a GFX Initialization routine. At the ;; start of a game, the standard GFX file are ;; loaded into the respective spots. This is ;; sort of the "fallback" template for all GFX. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; incsrc SMB1/GFX/Static.asm InitGFX: PHB PHP PHK PLB REP #$10 SEP #$20 LDA.b #$80 ;\ STA.w $2115 ;|This mostly just sets up some pre-DMA LDA.b #$7F ;|trasnfer stuff, like disabling interrupts STA.w $4304 ;|and selecting transfer locations. STA.b !Raw_Bk ;| LDX.w #$1801 ;| STX.w $4300 ;| LDX.w #!MainGFX ;| STX.b !Raw_Lo ;/ LDY.w #$0010 - LDX.w InitGFX_Table1,y ;\ JSR DoDecompress ;|This nifty little loop gets the default GFX files LDX.w InitGFX_Table2,y ;|from InitGFX_Table1, decompresses their GFX, stores STX.w $2116 ;|it !MainGFX, then sets up a DMA transfer to a location LDX.w #!MainGFX ;|selected by InitGFX_Table2. The idea is to set up the STX.w $4302 ;|default GFX for the game. LDX.w #$1000 ;|See SMB1/GFX/Static.asm for tables. STX.w $4305 ;| LDA.b #$01 ;| STA.w $420B ;| REP 2 : DEY ;| BPL - ;/ LDX.w #!MiscGFX ;\ STX.b !Raw_Lo ;|!MiscGFX is a RAM location with a GFX file designed to never LDX.w #$0026 ;|change (unless you want it to). The purpose of this DMA transer JSR DoDecompress ;|is to take the 2BPP GFX from GFX file $26 and permanently save it. LDX.w #$5000 ;|It's used mostly in the status bar, but also for the water animation STX.w $2116 ;|in underwater levels. LDX.w #!MiscGFX+$800 ;|Because it's only $800 bytes, I'm considering making this a raw GFX STX.w $4302 ;|ROM location to free up some RAM. You free to modify this and do so LDX.w #$0800 ;|yourself. Please don't ask me how though. Like I said before, modifcation STX.w $4305 ;|is for experience users only. LDA.b #$01 ;| STA.w $420B ;/ LDY.w #$0006 - LDX.w AnimGFX_Table1,y ;\ STX.b !Raw_Lo ;|This routine is very straightforward: Load the animated GFX RAM location, LDX.w AnimGFX_Table2,y ;|then the GFX file to store there, then just decompress it. Note that we're JSR DoDecompress ;|not doing a DMA transfer. Just writing the animated GFX to the desired RAM location. REP 2 : DEY ;|The game takes care of the actual anmation when the event occurs. BPL - ;/ LDX.w #!PrincessGFX ;\ STX.b !Raw_Lo ;|This is also straightforward: Take GFX file $000E (saved princess cutscene GFX) and LDX.w #$000E ;|save it to #!PrincessGFX. The game will load it when the even occurs. JSR DoDecompress ;/ PLP PLB RTL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This routine aims at inserting custom GFX ;; into the DMA when they are needed. The only ;; two cutscenes I've noticed are needed are ;; Game Over and Time Up screens. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LoadCutsceneGFX: PHP PHB PHK PLB REP #$10 SEP #$20 LDA.b #$80 ;\ STA.w $2115 ;|Sets up a DMA transfer for the cutscene. LDA.b #$7F ;|If you are unsure why !MainGFX is always used, STA.w $4304 ;|it's because it's not a static RAM table. It's use STA.w !Raw_Bk ;|is to have a GFX page exist for a DMA transfer, so it LDX.w #$1801 ;|can be used pretty much at any time. The othr RAM tables STX.w $4300 ;|however are supposed to be static (you can change them LDX.w #!MainGFX ;|if you have a way which suites what you need done). STX.b !Raw_Lo ;| STX.w $4302 ;/ LDX.w #$0011 ;GFX011 is the Game Over/Time Up GFX JSR DoDecompress LDX.w #$3400 STX.w $2116 LDX.w #$1000 STX.w $4305 LDA.b #$01 STA.w $420B LDX.w #!MainGFX+$800 STX.w $4302 LDX.w #$001B ;GFX01B also has Game Over/Time Up GFX JSR DoDecompress LDX.w #$2C00 STX.w $2116 LDX.w #$0800 STX.w $4305 LDA.b #$01 STA.w $420B PLB PLP RTL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This is the grand daddy of the custom GFX loading. ;; This routine is the core of getting all GFX from pointers ;; and loading them to their respective levels. So as ;; you will guess, it's pretty long and probably a little ;; complex. But it shouldn't be too bad to get through ;; most of it. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LoadLevelGFX: PHP PHB PHK PLB REP #$10 SEP #$20 PEI (!Raw_Lo) ;\ PEI (!Raw_Bk) ;|These values need to be re-pushed onto the stack. PEI (!Pointer_Hi) ;/ LDX.w #!MainGFX ;\ STX.b !Raw_Lo ;|Sets up !MainGFX for several DMA Transfers. LDA.b #$7F ;|The objective of the following loop is to load STA.b !Raw_Bk ;|the level pointer, determine the GFX file, decompress STA.w $4304 ;|that GFX page, and do a DMA transfer to it's respective LDA.b #$80 ;|location. Then repeat for all level GFX (objects and sprites). STA.w $2115 ;|This was not an easy algorithm to set up and I'm sure LDX.w #$1801 ;|it could use some work. STX.w $4300 ;/ LDY.w #$000E - REP #$30 ;We keep the REP in the loop because the status register will change. TYA ;\ REP 7 : ASL A ;|The pointer is now a multiple of $100 bytes (for each loop index). STA.b !Pointer_Lo ;/ LDA.w $0750 ;\ AND.w #$007F ;|We now add to the pointer index the current level value. CLC ;|We're not done yet though, there are other factors at play. ADC.b !Pointer_Lo ;| STA.b !Pointer_Lo ;/ LDA.w $07FC ;\ AND.w #$0002 ;|$07FC is the "Difficult Quest" flag. I've made some major modifications BEQ + ;|to allow for a whole new quest (set by 2 or 3). Therefore, levels in this LDA.w #$0080 ;|region will add $80 bytes to the index CLC ;| ADC.b !Pointer_Lo ;/ + ASL A ;We double the index because it's relative to words, not bytes. TAX LDA.l GFX_Table1,x ;\ CMP.w #$FFFF ;|A lot is going on here, so it may be a tad confusing. First, we check BEQ + ;|GFX_Tabale1 indexed by the conditions we made earlier. If the value is AND.w #$0FFF ;|$FFFF, that means no value has been selected so we go to the default (which TAX ;|is selected from GFX_Table3, which matches InitGFXTable1, but with switched BRA ++ ;|values to match the indexes). If it isn't $FFFF, then our value is the GFX + ;|index, then we move on to decompress it. LDX GFX_Table3,y ;/ ++ SEP #$20 ;This is why we keep the REP #$30 in the loop at the beginning. JSR DoDecompress ;\ LDX.w GFX_Table2,y ;|We now have the GFX page decompressed to !MainGFX. So we look up STX.w $2116 ;|GFX_Table2 to determine which VRAM address we will store it to. LDX.w #!MainGFX ;|The rest is just a standard DMA transfer. STX.w $4302 ;| LDX.w #$1000 ;| STX.w $4305 ;| LDA.b #$01 ;| STA.w $420B ;/ REP 2 : DEY BPL - REP #$30 LDA.w $0750 ;Now you may be wondering what this extra routine is for when it pretty much AND.w #$007F ;follows the pattern of the loop that came before it. The third GFX file in VRAM STA.b !Pointer_Lo ;is the layer 2 BG for levels. In SMAS SMB1, bonus levels switch the BG if you are LDA.w $07FC ;Luigi. So what this does search for a flag in the ObjGFX_Table3 table (set from $1000). AND.w #$0002 ;If it is set, the routine checks if luigi is playing. If both are true, it increments BEQ + ;the GFX file and selects that one instead. You can use this to your advantage. If you LDA.w #$0080 ;want to change the BG for when it's Luigi, make sure the next GFX file is the desired one. CLC ;You can use example of this code for other cool effects too. =P ADC.b !Pointer_Lo + ASL A TAX LDA.w ObjGFX_Table3,x CMP.w #$FFFF BEQ LoadPlayerGFX PHA AND.w #$0FFF TAX PLA AND.w #$1000 STA.w $02F8 BEQ + LDA.w $0EC2 AND.w #$00FF BEQ + INX + SEP #$20 JSR DoDecompress LDX.w #$2000 STX.w $2116 LDX.w #!MainGFX STX.w $4302 LDX.w #$1000 STX.w $4305 LDA.b #$01 STA.w $420B LoadPlayerGFX: LDY.w #$0002 - LDX.w PlayerGFX_Table1,y ;The objective of this loop is to determine which player STX.b !Raw_Lo ;is selected, obtain the GFX for said player, then store it LDX.w PlayerGFX_Table2,y ;!PlayerGFX1 and !PlayerGFX2. Note that the two values don't LDA.w $0EC2 ;mean GFX for Player 1 and Player 2 repectively. The player GFX AND.b #$FF ;take two pages. So the addresses represent the first and second BEQ + ;page respectively. Thus, it is necessarry to call this at level LDX.w PlayerGFXTable2+4,y ;load and not during the Initialization routine. + ;This is another event when I feel I should have raw GFX and just CPX.w #$FFFF ;handle it during the init routine. It would free up more RAM and BEQ + ;save on loading time. JSR DoDecompress + DEY DEY BPL - PLX : STX !Pointer_Hi ;\ PLX : STX !Raw_Bk ;|Pulling the orignal value back from the stack PLX : STX !Raw_Lo ;/ PLB PLP RTL