Skip to content

Commit

Permalink
fixed MBC1+ROM larger bigger than 1MB
Browse files Browse the repository at this point in the history
  • Loading branch information
marcrobledo committed Feb 10, 2024
1 parent 04c3900 commit 17b4ad5
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ DEF BANK0_FREE_SPACE EQU $3fd0



; MBC type
; --------
; (see more here: https://gbdev.io/pandocs/MBCs.html)
; MBC needs to be here specified in these special cases:
; - MBC1 + expanded ROM to 1Mb or bigger (Zelda Link's Awakening [non-DX])
; - (to-do!) MBC5 + expanded ROM to 8Mb
; other cases will just ignore this value
; keep in mind that, in these special cases, the hook subroutine will grow
; from 16 bytes to 25 bytes!
DEF MBC EQU 5



; NEW CODE LOCATION
; -----------------
; We need an empty bank to store all new code plus border data, which will be
Expand All @@ -37,8 +50,8 @@ DEF BANK0_FREE_SPACE EQU $3fd0
; - 64kb --> bank $04
; - 128kb --> bank $08
; - 256kb --> bank $10
; - 512kb --> bank $20
; - 1024kb --> bank $40
; - 512kb --> bank $21 (bank $20 needs more code to be accessed in MBC1)
; - 1024kb --> bank $41 (bank $40 needs more code to be accessed in MBC1)
DEF SGB_CODE_BANK EQU $10


Expand Down
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
71 changes: 71 additions & 0 deletions examples/zelda_links_awakening/settings.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
; ------------------------------------------------------------------------------
; Super Game Boy border injector for Zelda Link's Awakening (non-DX)
; by Marc Robledo 2024
;
; More info at https://github.com/marcrobledo/super-game-boy-border-injector
; ------------------------------------------------------------------------------



; GAME BOOT OFFSET
; ----------------
; Put here the game's boot jp offset found in in $0101.
; Usually $0150, but could be different depending on game.
DEF GAME_BOOT_OFFSET EQU $0150



; BANK 0 ROM FREE SPACE
; ---------------------
; 16 bytes in bank 0 are needed for the game's boot hook subroutine.
; Hopefully, there should be enough space at the end of bank 0 or in the
; interruption or rst vector ($0000-$00ff).
; In the worst scenario, you will need to carefully move some code/data to
; other banks.
DEF BANK0_FREE_SPACE EQU $0005



; MBC type
; --------
; (see more here: https://gbdev.io/pandocs/MBCs.html)
; MBC needs to be here specified in these special cases:
; - MBC1 + expanded ROM to 1Mb or bigger (Zelda Link's Awakening [non-DX])
; - (to-do!) MBC5 + expanded ROM to 8Mb
; other cases will just ignore this value
; keep in mind that, in these special cases, the hook subroutine will grow
; from 16 bytes to 25 bytes!
DEF MBC EQU 1



; NEW CODE LOCATION
; -----------------
; We need an empty bank to store all new code plus border data, which will be
; quite big.
; If the game has no empty bank, just use a bank higher than the original
; game's bank number, RGBDS will expand the ROM and fix the header.
; Safe bank numbers, depending on original game's ROM size:
; - 32kb --> impossible to do it without changing MBC
; - 64kb --> bank $04
; - 128kb --> bank $08
; - 256kb --> bank $10
; - 512kb --> bank $21 (bank $20 needs more code to be accessed in MBC1)
; - 1024kb --> bank $41 (bank $40 needs more code to be accessed in MBC1)
DEF SGB_CODE_BANK EQU $21



; CUSTOM GB PALETTE
; -----------------
; set CUSTOM_GB_PALETTE_ENABLED to 1 if you want a custom GB palette for the
; entire game screen
; colors are RGB15 which means RGB components can go from 0 up to 31
; warning: even if set to 0, do not delete BUILD_CUSTOM_GB_PALETTE macro!
DEF CUSTOM_GB_PALETTE_ENABLED EQU 1
MACRO BUILD_CUSTOM_GB_PALETTE
RGB 27, 30, 25 ;color 0 (light)
RGB 17, 23, 14 ;color 1
RGB 6, 13, 10 ;color 2
RGB 1, 3, 4 ;color 3 (dark)
ENDM
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ <h1 class="hide">Super Game Boy Border Injector</h1>


<footer>
Super Game Boy Border Injector v0.2<br/>
Super Game Boy Border Injector v0.3<br/>
made with <span class="love" title="Super love">Super <img src="web-injector/assets/octicon_heart.svg" alt="love" class="octicon" /></span> by <a href="https://www.marcrobledo.com" target="_blank">Marc Robledo</a>
<img src="web-injector/assets/octicon_github.svg" alt="GitHub logo" class="octicon" /> <a href="https://github.com/marcrobledo/super-game-boy-border-injector/" target="_blank">See sourcecode in GitHub</a>
</footer>
Expand Down
10 changes: 10 additions & 0 deletions src/main.asm
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,19 @@ boot_hook:
ld a, BANK(sgb_init)
ld [rROMB0], a

IF MBC == 1 && SGB_CODE_BANK>=$20
ld a, ((BANK(sgb_init) & %01100000) >> 5)
ld [rRAMB], a
ENDC

call sgb_init

ld [rROMB0], a
IF MBC == 1 && SGB_CODE_BANK>=$20
xor a
ld [rRAMB], a
ENDC

pop af
jp boot_original

Expand Down
19 changes: 16 additions & 3 deletions src/settings.asm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ DEF BANK0_FREE_SPACE EQU $3ff0



; MBC type
; --------
; (see more here: https://gbdev.io/pandocs/MBCs.html)
; MBC needs to be here specified in these special cases:
; - MBC1 + expanded ROM to 1Mb or bigger (Zelda Link's Awakening [non-DX])
; - (to-do!) MBC5 + expanded ROM to 8Mb
; other cases will just ignore this value
; keep in mind that, in these special cases, the hook subroutine will grow
; from 16 bytes to 25 bytes!
DEF MBC EQU 5



; NEW CODE LOCATION
; -----------------
; We need an empty bank to store all new code plus border data, which will be
Expand All @@ -37,9 +50,9 @@ DEF BANK0_FREE_SPACE EQU $3ff0
; - 64kb --> bank $04
; - 128kb --> bank $08
; - 256kb --> bank $10
; - 512kb --> bank $20
; - 1024kb --> bank $40
DEF SGB_CODE_BANK EQU $20
; - 512kb --> bank $21 (bank $20 needs more code to be accessed in MBC1)
; - 1024kb --> bank $41 (bank $40 needs more code to be accessed in MBC1)
DEF SGB_CODE_BANK EQU $21



Expand Down
49 changes: 37 additions & 12 deletions web-injector/app/assembled-code.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
export const ASSEMBLED_HOOK=[
0xf5, //push af
0x3e, 'bank_number', //ld a, sgb_init_bank
0xea, 0x00, 0x20, //ld [0x2000], a
0xcd, 0x00, 0x40, //call 0x4000
0xea, 0x00, 0x20, //ld [0x2000], a
0xf1, //pop af
0xc3, 'entry_point_low', 'entry_point_high' //jp original_entry_point
export const ASSEMBLED_HOOK_INFO=[
//common case
{
code:[
0xf5, //push af
0x3e, 'rom_bank_number', //ld a, sgb_init_bank
0xea, 0x00, 0x20, //ld [0x2000], a
0xcd, 0x00, 0x40, //call 0x4000
0xea, 0x00, 0x20, //ld [0x2000], a
0xf1, //pop af
0xc3, 'entry_point_low', 'entry_point_high' //jp original_entry_point
]
},
//MBC1 + larger than 512kb
//requires additional code to write to $4000 (ROM bank upper bits)
{
code:[
0xf5, //push af
0x3e, 'rom_bank_number', //ld a, sgb_init_bank_lowerbits
0xea, 0x00, 0x20, //ld [0x2000], a
0x3e, 'rom_bank_number_upperbits', //ld a, sgb_init_bank_upperbits
0xea, 0x00, 0x40, //ld [0x4000], a
0xcd, 0x00, 0x40, //call 0x4000
0xea, 0x00, 0x20, //ld [0x2000], a
0xaf, //xor a
0xea, 0x00, 0x40, //ld [0x4000], a
0xf1, //pop af
0xc3, 'entry_point_low', 'entry_point_high' //jp original_entry_point
]
}
];


export const ASSEMBLED_HOOK_BANK_NUMBER=ASSEMBLED_HOOK.indexOf('bank_number');
export const ASSEMBLED_HOOK_ENTRY_POINT=ASSEMBLED_HOOK.indexOf('entry_point_low');
ASSEMBLED_HOOK_INFO.forEach(function(assembledInfo){
assembledInfo.patchOffsets={
romBankNumber: assembledInfo.code.indexOf('rom_bank_number'),
romBankNumberUpperbits: assembledInfo.code.indexOf('rom_bank_number_upperbits'),
entryPoint: assembledInfo.code.indexOf('entry_point_low')
}
});

// assembled code from "SGB Bank - Code" SECTION in main.asm
export const ASSEMBLED_SGB_CODE=[
Expand Down
76 changes: 53 additions & 23 deletions web-injector/app/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FileParser } from './gb-parser.js'
import { PaletteGB, PaletteSNES, ColorRGB15, Tile4BPP, MapSNES } from './console-graphics.js'
import { EXAMPLE_GB_TILE_DATA, EXAMPLE_GB_MAP_DATA, SGB_DEFAULT_PALETTE } from './preview-example-data.js'
import { ASSEMBLED_HOOK, ASSEMBLED_HOOK_BANK_NUMBER, ASSEMBLED_HOOK_ENTRY_POINT, ASSEMBLED_SGB_CODE } from './assembled-code.js'
import { ASSEMBLED_HOOK_INFO, ASSEMBLED_SGB_CODE } from './assembled-code.js'


/*
Expand All @@ -25,6 +25,7 @@ var pickerStatus={
};
var bufferedFiles={};
var currentRomName;
var currentRomType;

function setPickerStatus(id, status, message){
pickerStatus[id]=status;
Expand Down Expand Up @@ -218,7 +219,8 @@ function checkFileRom(file){
var result={
supported:false,
title:'Invalid or incompatible Game Boy ROM',
banks: nBanks
mbc:0,
banks: 0
}

try{
Expand Down Expand Up @@ -248,7 +250,9 @@ function checkFileRom(file){
for(var i=0; i<CARTRIDGE_TYPES.length; i++){
if(CARTRIDGE_TYPES[i].id===byteType){
result.supported=CARTRIDGE_TYPES[i].supported;
result.mbc=CARTRIDGE_TYPES[i].mbc;
result.title=CARTRIDGE_TYPES[i].title;
result.banks=nBanks;
break;
}
}
Expand All @@ -257,6 +261,7 @@ function checkFileRom(file){
if(!result.supported){
throw new Error(message);
}
currentRomType=result;

var fileSize=nBanks*16384;
if((fileSize / 1048576) < 1)
Expand Down Expand Up @@ -340,10 +345,13 @@ function checkFileSGB(file){



function findRepeatBytes(file, offset, len){
function buildRepeatData(len, data){
return Array(len).fill(data);
}
function findRepeatBytes(file, offset, len, repeatMany){
file.seek(offset);
var b=file.readByte();
return findBytes(file, {offset:file.getOffset(), len:1, data:Array(len - 1).fill(b), reverse: false});
return findBytes(file, {offset:file.getOffset(), len:repeatMany || 1, data:buildRepeatData(len -1, b), reverse: false});
}

function findBytes(file, obj){
Expand Down Expand Up @@ -372,12 +380,6 @@ function findBytes(file, obj){
return null;
}

// places the injector will look for free space
const VALID_BANK0_FREE_SPACE=[
{offset:0x4000, len:0x80, data:[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], reverse: true},
{offset:0x0000, len:0xf0, data:[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], reverse: false},
{offset:0x0000, len:0xf0, data:[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], reverse: false}
];



Expand All @@ -388,15 +390,10 @@ function buildROM(){
rom.seek(0x0101);
if(rom.readByte()!==0xc3) //jp
throw new Error('Game has no jp entry point');

var freeSpace0=null;
for(var i=0; i<VALID_BANK0_FREE_SPACE.length && freeSpace0===null; i++){
freeSpace0=findBytes(rom, VALID_BANK0_FREE_SPACE[i]);
}
if(freeSpace0===null)
throw new Error('Bank 0 has no free space');

console.log('free space found in bank 0: $'+freeSpace0.toString(16));


//search a free bank, expand rom if needed
var freeBankX=null;
for(var i=0x4000; i<rom.length() && !freeBankX; i+=0x4000){
if(findRepeatBytes(rom, i, 0x4000)){
Expand Down Expand Up @@ -426,6 +423,36 @@ function buildROM(){
rom.seek(0x0148);
rom.writeByte(Math.log2(nBanks) - 1);
}


var assembledHookInfo;
if(currentRomType.mbc===1 && freeBankX>=0x20){
console.log('using assembled code for MBC1+ROM bigger than 1MB');
if(freeBankX===0x20 || freeBankX===0x40 || freeBankX===0x60){
//banks $20, $40, $60 need additional code to be accesed
//use $21, $41 or $61 instead
freeBankX++;
}

assembledHookInfo=ASSEMBLED_HOOK_INFO[1];
}else{
console.log('using common assembled code');
assembledHookInfo=ASSEMBLED_HOOK_INFO[0];
}


//find free space in bank 0
var freeSpace0=findBytes(rom, {offset:0x4000, len:0x80, data:buildRepeatData(assembledHookInfo.code.length, 0xff), reverse: true});
if(freeSpace0===null)
freeSpace0=findBytes(rom, {offset:0x0000, len:0xf0, data:buildRepeatData(assembledHookInfo.code.length, 0xff), reverse: false});
if(freeSpace0===null)
freeSpace0=findBytes(rom, {offset:0x0000, len:0xf0, data:buildRepeatData(assembledHookInfo.code.length, 0x00), reverse: false});

if(freeSpace0===null)
throw new Error('Bank 0 has no free space (need '+assembledHookInfo.code.length+' bytes)');

console.log('free space found in bank 0: $'+freeSpace0.toString(16));



//add SGB flags to header
Expand All @@ -442,10 +469,13 @@ function buildROM(){

//patch entry point hook
rom.seek(freeSpace0);
ASSEMBLED_HOOK[ASSEMBLED_HOOK_BANK_NUMBER]=freeBankX;
ASSEMBLED_HOOK[ASSEMBLED_HOOK_ENTRY_POINT]=originalEntryPoint & 0xff;
ASSEMBLED_HOOK[ASSEMBLED_HOOK_ENTRY_POINT + 1]=(originalEntryPoint >> 8) & 0xff;
rom.writeBytes(ASSEMBLED_HOOK);
assembledHookInfo.code[assembledHookInfo.patchOffsets.romBankNumber]=freeBankX;
assembledHookInfo.code[assembledHookInfo.patchOffsets.entryPoint]=originalEntryPoint & 0xff;
assembledHookInfo.code[assembledHookInfo.patchOffsets.entryPoint + 1]=(originalEntryPoint >> 8) & 0xff;
if(assembledHookInfo===ASSEMBLED_HOOK_INFO[1]){ //MBC1+1MB
assembledHookInfo.code[assembledHookInfo.patchOffsets.romBankNumberUpperbits]=(freeBankX & 0b01100000) >> 5;
}
rom.writeBytes(assembledHookInfo.code);

//write SGB code
rom.seek(freeBankX * 0x4000);
Expand Down Expand Up @@ -496,7 +526,7 @@ function buildROM(){
rom.writeByte(newChecksum & 0xff);


var newRomName=currentRomName.replace(/\.(gbc?)$/, ' (SGB Compatible).$1');
var newRomName=currentRomName.replace(/\.(gbc?)$/, ' (SGB Enhanced).$1');
var blob=new Blob([rom.getBuffer()], {type: 'application/octet-stream'});
saveAs(blob, newRomName);

Expand Down

0 comments on commit 17b4ad5

Please sign in to comment.