; Oink! ZX Spectrum 128 Patch
;
; Patch the original release of Oink!, which was a tape multi-load for the
; 48k ZX Spectrum, to use the Spectrum 128's extra memory so that the three
; sub-games are all loaded into RAM at once.
;
; Mark Incley, 16th February 2014.
; Adapted for Inkspector, 20th March 2023.
;
; Assemble with Inkspector 2.0.5 or later to produce a make_oink128.pok file.
;
; How to apply the resulting make_oink128.pok file
; ================================================
;
; 1.	Fire up a 128K Spectrum in your favourite Speccy emulator
;
; 2. 	Load the original 48K oink.tap or oink.tzx tape file
;
; 3.	When the main part of the game has loaded it will show the flashing Oink! picture
; 	and then sit there waiting for a key press (how convenient!)
;	-- DON'T PRESS A KEY JUST YET! -- just stop the tape.
;
; 4.	Load "make_oink128.pok" and apply the only trainer it contains:
;	"Patch Oink to Oink128 to remove tape multi-load"
;
; 5.	Back at the Speccy, if you then press a key, rather than going to the
;	main menu as the unpatched version does, it should attempt to load all
;	three sub games into the 128k's memory. Resume the tape playback.
;	Once all games have loaded the game should go to the main screen as usual,
;	displaying "Oink! 128k Ver." on the top line of the main menu confirming
; 	the .pok has been applied correctly.
;
; 6.	From then on, sub-games will be loaded from memory and not tape.
;	You will briefly see what looks like garbage appearing on screen
;	when the sub-games are loaded from memory. This is because the screen
;	is used as a temporary store as the games are copied out of the
;	128's extra RAM pages and into the 48K's regular memory.

; 0x4000 - 0x7FFF Bank 5
; 0x8000 - 0xBFFF Bank 2
; 0xC000 - 0xFFFF Bank 0 and 1, 3, 4 or 6 (see below)
;
; The sub-games have been designated thus:

; Bank 1 - Pete's Pimple
; Bank 3 - Rubbishman (part 1)
; Bank 4 - Tom Thug
; Bank 6 - Rubbishman (part 2)

		DEFINE LoadOinkFile	0x88EA
		DEFINE FileTable	0x8BEF	; word start, length
		DEFINE InitGame		0x80A2
		DEFINE ClearAttr	0x8AFE

		DEFINE FilePetesPimple	0x81
		DEFINE FileRubbishP1	0x84
		DEFINE FileRubbishP2	0x82
		DEFINE FileTomThug	0x83

		DEVICE ZXSPECTRUM48
		SAVEPOK 	"make_oink128.pok"
		POKTRAINER	"Patch Oink to Oink128 to remove tape multi-load"

; Replace the call to InitGame with a call to the 128k loader, which will jump to InitGame when it's done
		ORG	0x8052
		call	PatchedInitGame

; Replace "Input Selection" on the main menu with an indication that patch is present.
; It must be 15 characters long
		ORG	0x9527
TitleStart:
		db	"Oink! 128k Ver."
		ASSERT  $-TitleStart==15

; The 128k loader lives immediately after Oink's IM2 vector table which is located
; between 0xB600->0xB700 (and points to 0xB5B5)
		ORG	0xB701

PatchedInitGame:
		ld	hl,havePatched
		ld	a,(hl)
		or	a
		jr	nz,.patched

		inc	(hl)			; Flag the game as patched

		; Before we patch the file loader, use it to load all files to RAM
		call	LoadAllFiles

		ld	hl,LoadOinkFile
		ld	(hl),195		; jp LoadFileFromRAM
		inc	hl
		ld	(hl),LOW LoadFileFromRAM
		inc	hl
		ld	(hl),HIGH LoadFileFromRAM

.patched:	jp	InitGame		; jump back to the original Oink code to initialise the game


;  In: (currentFile) = file number 80-84h
; Out: hl = address
;      bc = length
GetFileEntry:	ld	a,(currentFile)
		add	a,a
		add	a,a
		ld	l,a
		ld	h,0
		ld	de,FileTable
		add	hl,de
		ld	e,(hl)
		inc	hl
		ld	d,(hl)
		inc	hl
		ld	c,(hl)
		inc	hl
		ld	b,(hl)		; bc = length
		ex	de,hl
		ret


LoadAllFiles:
		; Patch the file loader so that it always loads to address 0xC000
		; regardless of the values in the file's header
		ld	hl,0x8985
		inc	(hl)		; Ensure HL is moved on, as we're going
		inc	hl		; to clobber its "inc hl" with the following
		inc	hl		; code change:
		ld	(hl),1		; ld bc,0xc000
		inc	hl
		ld	(hl),0
		inc	hl
		ld	(hl),0xc0

		ld	a,FilePetesPimple
		call	LoadFile

		ld	a,FileRubbishP1
		call	LoadFile

		ld	a,FileRubbishP2
		call	LoadFile

		ld	a,FileTomThug
		call	LoadFile

		;
		; Fall through to PageRAMZero
		;


PageRAMZero:	xor	a

		;
		; Fall through to PageRAM
		;

; PageRAM
;	 In: al = RAM bank
;	Out: Memory bank paged in

PageRAM:	or	00010000b
		ld	bc,7ffdh
		out	(c),a
		ret


LoadFile:	ld	(currentFile),a
		call	SelectFileRAM

.retry:		ld	a,(currentFile)
		call	LoadOinkFile	; Load the file directly into the correct RAM page
		jr	nc,.retry	; Any error try to re-load. NB carry = success
		ret


SelectFileRAM:	and	7
		ld	l,a
		ld	h,0
		ld	de,RAMPages
		add	hl,de
		ld	a,(hl)		; a = source RAM page
		jr	PageRAM


LoadFileFromRAM:
		di

; We need to handle Rubbishman differently, as it is loaded in two parts:
;
; Part 1 (file # 84h) is loaded to 0xb801 then moved down to 0x5b00
; Part 2 (file # 82h) is loaded to 0xb801
;
; Because we're using the 0x5b00 area (as well as the screen) as a temporary buffer
; to hold data from paged RAM, we can't use that approach any more.
;
; Instead, we fake the loading of file 84h, which will result in garbage being copied to 0x5b00
; by the original code, but then we can handle both parts of the scenario when file 82h
; is loaded afterwards. Sneaky huh?

		cp	FileRubbishP1	; Pretend rubbishman part 1 loaded OK - we'll load it properly with part 2
		jr	z,.out

		cp 	FileRubbishP2	; Rubbishman part 2?
		jr	nz,.load	; No, it's Pete's Pimple or Tom Thug

		call	.load		; Load file #82 (part 2) directly into 0xb801

		ld	a,FileRubbishP1	; Get the entry for Rubbishman part 1
		ld	(currentFile),a
		call	SelectFileRAM
		call	GetFileEntry
		ld	hl,0xc000	; Copy it down to 0x5b00
		ld	de,0x5b00
		ldir
		call	PageRAMZero	; Restore memory page and we're done
		jr	.out

.load:		ld	(currentFile),a
		call	SelectFileRAM
		call	GetFileEntry
		push	hl
		ld	hl,0xc000
		ld	de,0x4000
		push	bc
		push	de

		ldir		; Copy game to screen

		call	PageRAMZero	; Select "normal" page for 0xc000

		pop	hl
		pop	bc
		pop	de
		ldir		; Copy from screen to page 0

.out:		scf		; Return with CY = success
		ret


havePatched	db	0
currentFile	db	0
RAMPages	db	0, 	1, 	3, 	4, 	6

		DISPLAY "  Code End: ", $
		DISPLAY "Patch size: ", $-PatchedInitGame
		END	PatchedInitGame
