Region autodetection for PAL/NTSC

Mugi

Member
First of all, i take no credit from this, i didn't even know this issue existed until a friend of mine who was poking around with my sources noticed it,
and secondly, the code for this is available in nesdev, from where it was almost directly copypasted.

I'm just sharing this since it's something that should exist in the engine by default and will make PAL people's life easier.

what this script does is sets the engine to autodetect the system you're running on and properly set the audio driver to PAL/NTSC/dendy mode to make the sound play as intended regardless of the region the game is being played in.
by default, nesmaker roms are hardcoded into NTSC mode, and playing such a rom in a PAL console will slow down the audio and make it sound different than it should sound.

first of all, you make a new .asm file: getTVSystem.asm

Code:
;
; NES TV system detection code
; Copyright 2011 Damian Yerrick
;
; Copying and distribution of this file, with or without
; modification, are permitted in any medium without royalty provided
; the copyright notice and this notice are preserved in all source
; code copies.  This file is offered as-is, without any warranty.
;
;.export getTVSystem
; using vBlankTimer as nmis
;.importzp nmis

;.align 32  ; ensure that branches do not cross a page boundary

;;
; Detects which of NTSC, PAL, or Dendy is in use by counting cycles
; between NMIs.
;
; NTSC NES produces 262 scanlines, with 341/3 CPU cycles per line.
; PAL NES produces 312 scanlines, with 341/3.2 CPU cycles per line.
; Its vblank is longer than NTSC, and its CPU is slower.
; Dendy is a Russian famiclone distributed by Steepler that uses the
; PAL signal with a CPU as fast as the NTSC CPU.  Its vblank is as
; long as PAL's, but its NMI occurs toward the end of vblank (line
; 291 instead of 241) so that cycle offsets from NMI remain the same
; as NTSC, keeping Balloon Fight and any game using a CPU cycle-
; counting mapper (e.g. FDS, Konami VRC) working.
;
; nmis is a variable that the NMI handler modifies every frame.
; Make sure your NMI handler finishes within 1500 or so cycles (not
; taking the whole NMI or waiting for sprite 0) while calling this,
; or the result in A will be wrong.
;
; @return A: TV system (0: NTSC, 1: PAL, 2: Dendy; 3: unknown
;         Y: high byte of iterations used (1 iteration = 11 cycles)
;         X: low byte of iterations used
getTVSystem:
    ldx #0
    ldy #0
    lda vBlankTimer
nmiwait1:
    cmp vBlankTimer
    beq nmiwait1
    lda vBlankTimer

nmiwait2:
    ; Each iteration takes 11 cycles.
    ; NTSC NES: 29780 cycles or 2707 = $A93 iterations
    ; PAL NES:  33247 cycles or 3022 = $BCE iterations
    ; Dendy:    35464 cycles or 3224 = $C98 iterations
    ; so we can divide by $100 (rounding down), subtract ten,
    ; and end up with 0=ntsc, 1=pal, 2=dendy, 3=unknown
    inx
    bne nminoiny
    iny
nminoiny:
    cmp vBlankTimer
    beq nmiwait2
    tya
    sec
    sbc #10
    cmp #3
    bcc notAbove3
    lda #3
notAbove3:
	rts

and save it in: GameEngineData\Routines\Basic\InitializationScripts\getTVSystem.asm


next you go to GameEngineData\Routines\Basic\BankData and open Bank1B.asm.

replace the contents of the file with the following:

Code:
;PAL / NTSC Detection for GGsound
	.include "Routines\Basic\InitializationScripts\getTVSystem.asm"
;Music Bank
	.include ROOT\System\ggsound.asm
IntroSong:
	.include "Sound\AllSongs_WithSFX.asm"

and lastly, open Routines\Basic\MainASM.asm and replace it's contents with the following:

Code:
;1. iNES header
	.include "GameData\Constants.asm"
	
	.include ROOT\System\Header.asm
	
	

;2. Constants and variables
	;A. First, the constants
	.include "Sound\ggsound.inc"
	.include "GameData\macroList.asm
	.include "ScreenData\init.ini"

	.include SCR_MEMORY_MAP


	;C. then the BANK ASSIGNMENTS
	.include ROOT\System\AssignBanks.asm
;________________________________________
;4 The Reset
	.include ROOT\System\Reset.asm
	
	;; initialize 
	.include ROOT\InitializationScripts\InitLoads.asm
_____________
;Things are initialized.  Jump to the main game loop
;____________________________________________________
	LDA #$00
	STA gameState
	STA textboxHandler

	LDY #BANK_MUSIC
	JSR bankswitchY

	lda #1
	sta skipNMI
	jsr getTVSystem ; loads region instead of hardcoding it
	;lda #SOUND_REGION_NTSC ;or #SOUND_REGION_PAL, or #SOUND_REGION_DENDY
    sta sound_param_byte_0
	lda #0
	sta skipNMI

    lda #<(song_list)
    sta sound_param_word_0
    lda #>(song_list)
    sta sound_param_word_0+1
    lda #<(sfx_list)
    sta sound_param_word_1
    lda #>(sfx_list)
    sta sound_param_word_1+1
    lda #<(instrument_list)
    sta sound_param_word_2
    lda #>(instrument_list)
    sta sound_param_word_2+1
    ;lda #<dpcm_list
    ;sta sound_param_word_3
    ;lda #>dpcm_list
    ;sta sound_param_word_3+1
    jsr sound_initialize


	LDY prevBank
	JSR bankswitchY
nevermindPlaySong:
	LDA #SKIP_START_SCREEN
	BEQ dontSkipStartScreen
	;;;;
;;============================================================
	;;SKIP START SCREEN!
	;;; COMMENT THIS ALL OUT TO OBSERVE START SCREEN AGAIN
	;;; this script does the same as our "press start" function does on start screen,
	;;; except here, it does it automatically on startup
	;LDA #SKIP_START_SCREEN
	;BEQ dontSkipStartScreen
	
	LDA #STATE_START_GAME
	STA change_state
	LDA #%10000000
	STA gameHandler ;; turn sprites on
	;;;;;;;;;
	;;;;END SKIP START SCREEN!
;;============================================================	
dontSkipStartScreen:
	;PlaySong #$00
	JMP MainGameLoop
	
WaitFrame:
	
	
	inc sleeping
	sleepLoop:
		lda sleeping
		BNE sleepLoop
	
	
	RTS
	
;9 NMI
NMI:
	;first push whatever is in the accumulator to the stack
	
	PHA
	LDA doNMI
	BEQ dontSkipNMI
	JMP skipWholeNMI
dontSkipNMI:

	LDA #$01
	STA doNMI
	TXA
	PHA
	TYA
	PHA
	PHP
	
	LDA temp
	PHA ;STA NMItemp
	LDA temp1
	PHA ;STA NMItemp1
	LDA temp2
	PHA ;STA NMItemp2
	LDA temp3
	PHA ;STA NMItemp3
	LDA tempx
	PHA ;STA NMItempx
	LDA tempy
	PHA ;STA NMItempy
	LDA tempz
	PHA ;STA NMItempz

	LDA updateHUD_fire_Address_Lo
	PHA ;STA NMI_updateHud_AddLo
	LDA updateHUD_fire_Address_Hi
	PHA ;STA NMI_updateHud_AddHi
	LDA updateHUD_fire_Tile
	PHA ;STA updateHud_tile
	
	LDA temp16
	PHA ;STA NMItemp16
	LDA temp16+1
	PHA ;STA NMItemp16_plus_1
	
	LDA currentBank
	PHA ;STA NMIcurrentBank
	LDA prevBank
	PHA ;STA NMIprevBank
	LDA tempBank
	PHA ;STA nmiTempBank
	
	
	LDA chrRamBank
	PHA ;STA nmiChrRamBank
	
	LDA skipNMI
	BEQ dontSkipNMI2
	JMP skipNMIstuff
dontSkipNMI2:


	
	LDA #$00
	STA $2000
	LDA soft2001
	STA $2001
	
	;;;Set OAL DMA
	LDA #$00
	STA $2003
	LDA #$02
	STA $4014
	;; Load the Palette
	
	LDA soft2001
	BNE doScreenUpdates
	JMP skipScreenUpdates
doScreenUpdates:
	bit $2002
	LDA updatePalettes
	BNE doPaletteUpdates
	JMP +
doPaletteUpdates:
	.include ROOT\DataLoadScripts\LoadPalettes.asm
	JMP skipScreenUpdates
+
	LDA updateNametable
	BNE doUpdateNametable
	JMP +
doUpdateNametable:;; for nametable changes
	.include ROOT\DataLoadScripts\LoadTileUpdate.asm
	JMP skipScreenUpdates
+
	LDA UpdateAtt
	BNE doUpdateAtt
	JMP +
doUpdateAtt:
	.include ROOT\DataLoadScripts\LoadAttUpdate.asm
	JMP skipScreenUpdates
+


	
skipScreenUpdates:	
	;; always do hud update, otherwise
	;; it's possible that updates to tiles, attributes, or palettes
	;; will cause a skip in hud update.
	.include ROOT\DataLoadScripts\LoadHudData.asm

	LDA xScroll_hi
	AND #%00000001
	STA showingNametable

	
	LDA #CHECK_SPRITE_ZERO
	BEQ skipSprite0Check
	LDA gameState
	CMP #GS_MainGame
	BNE skipSprite0Check
	LDA gameHandler
	AND #%10000000
	BEQ skipSprite0Check
	LDA $0200
	CMP #SPRITE_ZERO_Y
	BNE skipSprite0Check
	;;; do sprite 0 check.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;	

  LDA #$00         ; start with no scroll for status bar
  STA $2005
  STA $2005
  
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000        ; start with nametable = 0 for status bar

  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001

;JMP +
WaitNotSprite0:
  lda $2002
  and #%01000000
  bne WaitNotSprite0   ; wait until sprite 0 not hit

WaitSprite0:
  lda $2002
  and #%01000000
  beq WaitSprite0      ; wait until sprite 0 is hit
  
    ldx #SPRITE_ZERO_HBLANK ;;; SET THIS TO A DIFFERENT VALUE FOR OFFSET TO AVOID PIXEL FLICKER
WaitScanline:
	dex
	bne WaitScanline

	LDA soft2001
	STA $2001
skipSprite0Check:
 
	LDA #%10010000
	ORA showingNametable
	STA $2000
	
	LDA xScroll
	STA $2005	;reset scroll values to zero
	LDA yScroll
	STA $2005	;reset scroll values to zero
skipNMIstuff:		


	
	DEC vBlankTimer
	INC randomSeed1
	;;return from this interrupt
	;; music player things
	
	LDA #$0
	STA sleeping

	LDA currentBank
	STA prevBank
	LDY #BANK_MUSIC  ;; switch to music bank
	JSR bankswitchY
	soundengine_update  
	LDY prevBank
	JSR bankswitchY	


	PLA
	STA chrRamBank
	PLA 
	STA tempBank
	PLA 
	STA prevBank
	PLA
	STA currentBank
	PLA
	STA temp16+1
	PLA
	STA temp16
	PLA
	STA updateHUD_fire_Tile
	PLA 
	STA updateHUD_fire_Address_Hi
	PLA 
	STA updateHUD_fire_Address_Lo
	PLA ;LDA NMItempz
	STA tempz
	PLA ;LDA NMItempy
	STA tempy
	PLA ;LDA NMItempx
	STA tempx
	PLA ;LDA NMItemp3
	STA temp3
	PLA ;LDA NMItemp2
	STA temp2
	PLA ;LDA NMItemp1
	STA temp1
	PLA ;LDA NMItemp
	STA temp
	
	LDA #$00
	STA doNMI
	
	PLP
	PLA
	TAY
	PLA
	TAX
skipWholeNMI:	
	PLA

	
	RTI
	
	
MainGameLoop:
;;;;;;;;=========HANDLE FRAME TIMING	
	JSR GamePadCheck
	
	LDA vBlankTimer
vBlankTimerLoop:
	CMP vBlankTimer
	BEQ vBlankTimerLoop
;;;;;;==========END HANDLE FRAME TIMING	
	;; always get input.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;



	;; handle stage changes.
	LDA currentBank
	STA prevBank
	LDY #$14
	JSR bankswitchY
	JSR HandleStateChanges
;	JSR HandleHudData
	LDY prevBank
	JSR bankswitchY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	JSR HandleScreenLoads
	JSR CheckForUpdateScreenData
	

	;JSR HandleFadeLevels
	;JSR HandleFades
	;JSR HandleUpdateNametable

	
	JSR HandleBoxUpdates


	JSR HandleMusic
	JSR HandleInput
	JSR HandleUpdateObjects
	JSR HandleRandomizing

	JSR HandleGameTimer
	
HandleActivateWarpFlag
	LDA activateWarpFlag
	BEQ +
	LDA #$00
	STA activateWarpFlag

	LDX player1_object
	DeactivateCurrentObject
	LDA #$01
	STA loadObjectFlag
	LDA warpMap
	sta currentMap
	clc
	ADC #$01
	STA temp
	GoToScreen navToScreen, temp, #$02
	JMP ++ ;; skip scroll
+	

	
	
	

	LDA loadingTilesFlag
	BEQ dontUpdateColumn
	TXA
	STA tempx
	JSR updateColumnTiles
	LDX tempx
	JMP ++
dontUpdateColumn:
	LDA testFlagThing
	BEQ +
	LDA tempTileUpdate_lo
	STA temp16
	LDA tempTileUpdate_hi
	STA temp16+1
	LDA tempChangeTiles
	STA updateTile_00
	LDA tempChangeTiles+1
	STA updateTile_01
	LDA tempChangeTiles+2
	STA updateTile_02
	LDA tempChangeTiles+3
	STA updateTile_03
	
	JSR HandleUpdateNametable
	LDA #$00
	STA testFlagThing
	STA tileCollisionFlag
+

	JSR HandleScroll
++

	
	
	;;;;;;;;;;;;;;;;;

	
	;;;;;;;;;;;;;;;
	
	
	JMP MainGameLoop
	
	RTS	
	
;;;;;;;;;;;;;;;;;;;;;
	.include "GameData\ScriptTables.asm"
	.include ROOT\System\IncludeSystemFunctions.asm
	.include "GameData\HUD_DEFINES.dat"

	
	
	
columnTableLo:
	.db #$00, #$02, #$04, #$06, #$08, #$0A, #$0C, #$0E
	.db #$10, #$12, #$14, #$16, #$18, #$1A, #$1C, #$1E
	.db #$00, #$02, #$04, #$06, #$08, #$0A, #$0C, #$0E
	.db #$10, #$12, #$14, #$16, #$18, #$1A, #$1C, #$1E
	
	
columnTableHi:
	.db #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20
	.db #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20
	.db #$24, #$24, #$24, #$24, #$24, #$24, #$24, #$24
	.db #$24, #$24, #$24, #$24, #$24, #$24, #$24, #$24
	
attrColumnTableLo:
	.db #$c0, #$c1, #$c2, #$c3, #$c4, #$c5, #$c6, #$c7
	.db #$c0, #$c1, #$c2, #$c3, #$c4, #$c5, #$c6, #$c7
attrColumnTableHi:
	.db #$23, #$23, #$23, #$23, #$23, #$23, #$23, #$23
	.db #$27, #$27, #$27, #$27, #$27, #$27, #$27, #$27
	
	
	
bitwiseLut:
	.db #%10000000, #%01000000, #%00100000, #%00010000, #%00001000, #%00000100, #%00000010, #%00000001
	
bitwiseLut16:
	.db #%10000000, #%01000000, #%00100000, #%00010000, #%00001000, #%00000100, #%00000010, #%00000001
	
directionTable:
	.db #%00110000, #%11110000, #%11000000, #%11100000, #%00100000, #%10100000, #%10000000, #%10100000

;12. Vectors
	.include ROOT\System\Vectors.asm


i dont have a hardware cartridge or a flasher to test this on real hardware, but this was tested to function correctly in mesen, fceux and fixnes and it does indeed function as intended and keeps the music properly playing even if you set the emulator
to run in PAL (50Hz) mode.
 

dale_coop

Moderator
Staff member
Awesome... thank you, Mugi for this tutorial. Will be useful for a lot of PAL NES console owners ;)
 

ZiROBEAT

New member
Thanks Mugi, this is exactly what I was looking for. Just tested it in emulator and on my PAL NES after flashing, works like a charm!
 

Dirk

Member
You can use a simple text editor and save your file as getTVSystem.asm instead of getTVSystem.txt. If your editor saves your file as getTVSystem.asm.txt you'll need to change the intended file type to all files or *.*.
I hope this answers what you were asking.


saveasasm.png


Also, a huge thank you to Mugi! As a PAL person I'm very happy to have found your post!
 

axbakk

Member
sweet, thank you.
Im a totally noob of this (making games), then we should not talk about programing language.. :)
But iam here and will try to learn on my way using nesmaker.

Yes of course a big thanks to Mugi. This really helps to us PAL people.

One thought tho.. what about game speed? if a make a game in nesmaker... will it be slowdowns playin it on a pal system?
 

Mugi

Member
yeah, the PAL slowdown will still be there. this will only set the sound engine mode properly in order to not have slow music, but it doesnt affect anything else.
that said, because this detects the region and stores that information in a variable (sound_region)
it can be used for adjusting game mechanics for each region separeately.

for example, in my game, i have a cutscene at the beginning where the character is lying on the ground, and a speech bubble comes up saying (...) before she wakes up,
and this is timed to exactly match the music so that the speech bubble goes away and she will jump up exactly when the music picks up the beat.

i made this by using 2 different game objects, one for NTSC region and one for PAL, and they are both timed differently.
the game code looks at the sound region and uses whichever object it needs, resulting in that the "cutscene" is perfectly in synch on both regions.

it is possibly to further refined this by making separate values for movement speed, and so on, and then changing them based on the sound region, but that's going to be a lot of work, and im not sure if im even gonna go that route at all
the slowdown on PAL machines is a bit annoying yeah. but at the same time, it's one of those things that just are a part of the system and im really not sure if i even want to remove it from my own game. :p
 

Dirk

Member
Mugi: great ideas, I have to write this stuff down somewhere ^^

I've played someone's demo and noticed slowdown. I later realized I had my emulator set to PAL, after setting it to NTSC the slowdowns where gone. It was not just that the whole game was slower, there were slowdowns that weren't present in the NTSC emulation.
I guess that they might have been present and just got really pronounced when I switched to PAL.
This is a bit annoying, because 3-4 enemies with random movement and no special AI shouldn't make your game slow down to a crawl.
 

Mugi

Member
that happens with all the nesmaker games by default. the engine is not capable of handling more than 4 objects on screen at once.
you will have to optimize stuff like collision and sprite drawing and whatnot a lot in order to make the engine capable of handling more objects.

in my game currently i've gotten that up to 5 objects + player so a maximum of 6 before slowdowns occur and that alone has been a long series of optimizations (although we haven't touched collision yet.)

tl;dr: it's something you might want to keep in mind, because getting rid of that wont be easy.

as far as PAL/NTSC slowdown differenceces go, there are literally none, but the fact that pal runs 25% slower makes the slowdowns appear more slow on pal for obvious reasons.
aside that it's literally the same code so it will slowdown just as much on both pal and NTSC (technically, pal should run even a bit better depending on your code because on PAL the NMI is longer, so there's more execution time.)
 

m8si

Member
Russian region. lol

I still haven't figured this out. As a workaround I made different versions for NTSC & PAL.
 

Jonny

Well-known member
Can't get this to work with 4.5.9 Any Help?
Hey! I know you asked about this a long time ago but if you haven't figured it out yet maybe this will help for 4.5.9...

Create your getTVSystem.asm file, just save it into System directory as there is no InitializationScripts folder for 4.5.9.

Scroll to the bottom of Bank1B and...
Code:
;;; ADD THIS LINE ;;;

.include ROOT\System\getTVSystem.asm

;;; BEFORE THIS LINE ;;;

.include ROOT\System\ggsound.asm

Then in your initialization.asm script do this...
Code:
;;; ADD THIS LINE ;;;

JSR getTVSystem

;;; BEFORE THIS LINE ;;;

;;; LDA #SOUND_REGION_NTSC ;;; <<< COMMENT OUT THIS LINE ;;;

That sould be it. Let me know if you have any problems because I had to do a few extra things for music bank switching so haven't tested for 1 music bank but should work fingers crossed. When testing in mesen between PAL and NTSC make sure you reset as it will stay slow/fast until initialisation is run again obviously. I'm only saying that because, like an idiot, I was wondering why it wan't working.
 
Top Bottom