User File #638568543377870726

Upload All User Files

#638568543377870726 - S3K Camhack for Bizhawk

s3camhack.lua
Game: Sonic 3 & Knuckles ( Genesis, see all files )
156 downloads
Uploaded 7/17/2024 11:05 PM by BenInSweden (see all 3)
Not perfect, not entirely sure how to fix the remaining bugs.
-- addresses
local addr_base   = 0xB000
local addr_mode   = 0xF600
local addr_inlev  = 0xB004
local addr_cam1   = 0xEE78
local addr_cam3   = 0xFDB8
local addr_cam4   = 0xF616
local addr_time   = 0xFE22
local addr_lockX  = 0xEE16
local addr_lockY  = 0xEE1A
local addr_camlock = 0xEE0B
local addr_camdelay = 0xEE24

local romaddr_renderrings = 0xEB86
local romaddr_renderobjects = 0x1B7F2
local romaddr_camlock = 0x1C0B0

local lastframe = 0

-- offsets
local offs_flags  = 4
local offs_posX   = 0x10
local offs_posY   = 0x14

-- constants
local SCREEN_W    = 320
local SCREEN_H    = 224
local LEVEL_H     = 2047
local SCROLLRATEX  = 32
local SCROLLRATEY  = 16
local FREEZE_SIZE = 18
local MAX_FRAMES  = 100
local OST_ENTRY_LENGTH = 0x4A

-- aliases
local rb   = mainmemory.readbyte
local rs16 = mainmemory.read_s16_be
local ru16 = mainmemory.read_u16_be
local rs32 = mainmemory.read_s32_be
local ru32 = mainmemory.read_u32_be
local wb = mainmemory.writebyte
local ws16 = mainmemory.write_s16_be
local wu16 = mainmemory.write_u16_be
local ws32 = mainmemory.write_s32_be
local wu32 = mainmemory.write_u32_be

-- buffers
local freezebuffer = {}
local cambuf = {}
local memorystate


local renderingcamhack

local pre_ring_code = memory.read_s16_be(romaddr_renderrings, "MD CART")
local pre_obje_code = memory.read_s16_be(romaddr_renderobjects, "MD CART")
local pre_camlock = memory.read_s16_be(romaddr_camlock, "MD CART")
gui.defaultPixelFont("gens")


local function CamHack()
	client.invisibleemulation(false)
	renderingcamhack = false
	local levelheight = ru32( 0xEEAA )
	local x      = rs16(addr_base + offs_posX)   -- character x position
	local y      = rs16(addr_base + offs_posY)   -- character y position
	local origx  = rs16(addr_cam1)               -- initial camera x position
	local origy  = rs16(addr_cam1 + 4)           -- initial camera y position
	local lockx  = rs16(addr_lockX)              -- right screen lock
	local locky  = rs16(addr_lockY)              -- Bottom screen lock
	local xx     = math.max(0, math.min(x - SCREEN_W / 2, lockx)) -- desired camera x position
	local yy     =             math.min(y - SCREEN_H / 2, locky)  -- desired camera y position
	local flags  = rb(addr_base + offs_flags)
	local timer  = rs32(addr_time)
	local deltaX = SCROLLRATEX
	local levelwrap = x > origx + (SCREEN_W * 2)
	local lastobject = 20
	local lagframes = 0
	local camdelay = rs16( addr_camdelay )
	local lastx = 0
	local lasty = 0


	-- first we need to adjust for latency
	memorystate = memorysavestate.savecorestate()
	-- memory.write_s16_be(romaddr_camlock, 0x4241, "MD CART")
	
    client.invisibleemulation(true)
    client.seekframe(emu.framecount() + 1)
	--[[
		gui.pixelText(10, 100, string.format(
		" flags: %s\n"..
		"0xF7CD: %4d\n"..
		" cam: %4d | %s\n"..
		"  y: %s | inl: %s\n",
		flags & 0x80 ~= 0, rb(0xF7CD),
		camdelay, x - origx <= SCREEN_W,
		(y - origy <= 240 or (y + LEVEL_H - origy <= SCREEN_H and origy >= LEVEL_H - SCREEN_H) or (origy < 0 and (y >= LEVEL_H + origy or y <= origy + SCREEN_H))),
		rb(addr_inlev)
		)
	)
	--]]--
	--	if flags && 0x80 ~= 0
	if ( flags & 0x80 ~= 0 and camdelay == 0 )
	or rb(0xF7CD) ~= 0
	or (
		camdelay == 0 and x - origx <= SCREEN_W
		and (
			y - origy <= 240
			-- going downward through level-wraps
			or (y + LEVEL_H - origy <= SCREEN_H and origy >= LEVEL_H - SCREEN_H)
			-- going upward through level-wraps
			or (origy < 0 and (y >= LEVEL_H + origy or y <= origy + SCREEN_H))
		)
	)
	then 
		-- if we have nothing to do then just output the frame and be on our way
		client.invisibleemulation(false)
		client.seekframe(emu.framecount() + 1)
		-- memory.write_s16_be(romaddr_camlock, pre_camlock, "MD CART")
		lastx = rs16(addr_cam1)
		lasty = rs16(addr_cam1 + 4)
		memorysavestate.loadcorestate(memorystate)
		memorysavestate.removestate(memorystate)
		return
	end

	-- set the camera no more than 640 pixels distant
	if math.abs(xx - origx) > SCREEN_W * 2 then
		origx = math.max(0, xx - SCREEN_W * 2)
	end
	local delayforcam = 0
	if camdelay then 
		-- if we're coming from camdelay then don't try to adjust the camera vertically
		yy = origy
		delayforcam = 3
		ws16( addr_camdelay, 0)
	end
	-- and always above target, because Sonic doesn't like externally forced upward scrolling
	if origy > yy then
		origy = yy - SCREEN_H * 2
	else
		origy = math.max(yy - SCREEN_H * 2, origy)
	end

	if xx < origx then
		deltaX = -deltaX
	end

	if lastx > origx and lastx < xx + SCROLLRATEX then
		xx = lastx
	end

	local numframes = 0 + math.floor(math.max(
		math.abs(xx - origx) / SCROLLRATEX,
		math.abs(yy - origy) / SCROLLRATEY,
		delayforcam
		)
	)
	--[[
	gui.pixelText(10, 100, string.format(
		" pos: %4d x %4d\n"..
		"orig: %4d | %4d x %4d | %4d\n"..
		" cam: %4d %4d %4d %4d\n"..
		"  xx: %4d | %4d yy: %4d | %4d\n"..
		"  nf: %4d ",
		x, y,
		origx, rs16(addr_cam1),
		origy, rs16(addr_cam1+4),
		lastx, lasty, delayforcam, camdelay,
		xx, math.max(0, x - SCREEN_W / 2),
		yy, y - SCREEN_H / 2,
		numframes))
	--]]--
	if numframes > MAX_FRAMES then
		numframes = MAX_FRAMES
	end
	for i = 1, FREEZE_SIZE do
		freezebuffer[i] = ru32(addr_base + i*4)
	end

	if levelwrap then
		memory.write_s16_be(romaddr_renderrings, 0x4E75, "MD CART")
		memory.write_s16_be(romaddr_renderobjects, 0x4E75, "MD CART")
	end
	lagframes = 0
	for v = 0, 3 do
		cambuf[v] = ru32(0xEE0C + v*4)
	end
	local orignumframes = numframes
	for frame = 1, MAX_FRAMES do
		if frame >= numframes then break end
		if rb(addr_mode) ~= 0x0c then break end
		if origy < -SCROLLRATEY then origy = -SCROLLRATEY end
		if delayforcam == numframes and xx == rs16(addr_cam1) then break end
		renderingcamhack = true
		wb(addr_camlock, 1)
		ws16(addr_cam1,     origx)
		ws16(addr_cam1 + 4, origy)
		
		ws32(addr_time, timer)
		for v = 1, FREEZE_SIZE do
			wu32(addr_base + v*4, freezebuffer[v])
		end
		for v = 0, 3 do
			wu32(0xEE0C + v*4, cambuf[v])
		end
				
		client.seekframe(emu.framecount() + 1)
		
		

		if numframes < MAX_FRAMES and emu.islagged() then
			numframes = numframes + 1
			lagframes = lagframes + 1
			if numframes >= 15 and lagframes >= 10 then
				break
			end
		else
			-- the game doesn't like being forced to scroll up
			if yy > origy then
				origy = origy + math.min(SCROLLRATEY, yy - origy)
			end
		
			if xx ~= origx then
				if math.abs(xx - origx) <= SCROLLRATEX then
					origx = xx
				else
					origx = origx + deltaX
				end
			end
		end
	end
	--[[
	gui.pixelText(10, 100, string.format(
		" pos: %4d x %4d\n"..
		"orig: %4d | %4d x %4d | %4d\n"..
		" cam: %4d %4d %4d\n"..
		"  xx: %4d | %4d yy: %4d | %4d\n"..
		"  nf: %4d | orignumframes: %4d",
		x, y,
		origx, rs16(addr_cam1),
		origy, rs16(addr_cam1+4),
		rs32(addr_cam3), rs32(addr_cam4), rs32(addr_cam4+4),
		xx, math.max(0, x - SCREEN_W / 2),
		yy, y - SCREEN_H / 2,
		numframes, orignumframes))

	]]--
	client.invisibleemulation(false)
	client.seekframe(emu.framecount() + 1)
	client.invisibleemulation(true)
	lastx = rs16(addr_cam1)
	lasty = rs16(addr_cam1 + 4)
	memory.write_s16_be(romaddr_renderrings, pre_ring_code, "MD CART")
	memory.write_s16_be(romaddr_renderobjects, pre_obje_code, "MD CART")
	-- memory.write_s16_be(romaddr_camlock, pre_camlock, "MD CART")
	memorysavestate.loadcorestate(memorystate)
	memorysavestate.removestate(memorystate)
	
	--client.invisibleemulation(false)
end
while true do
	if emu.framecount() > lastframe then
		CamHack()
	end
	lastframe = emu.framecount()
	emu.frameadvance()
	gui.clearGraphics()
end