1 2
10 11
Editor, Experienced Forum User, Skilled player (1098)
Joined: 9/27/2008
Posts: 1075
Okay, I am equipped with knowledge of opcodes! Been studying the RAM a touch. It helps that, if the game encounters a BRK, we get an immediate RTI, so execution continues from where it left off.
0000 * 0002: Unused in build mode?
0003 - 0007: Sound related. Zeroes out every frame.
0008 * 0011: Not used during build mode. Part of it apparently used for menus.
0012       : Used for something. It won't get in our way, at least.
0013 * 003F: Not used during build mode. No reads, no writes. Anything in the way can just be corrupted right out. Something in there is used for menus, though.
0040 - 0041: Apparently tested for specific IDs. Normally zero.
0042 * 0055: More unused stuff in gameplay.
0056 - 0057: Tested for something. Not sure.
0058 * 005E: Looks unused.
005F - 0072: Most of the stuff here is transferred to 0x002100 region. Not precisely sure how the game would look when this is corrupted. It helps that no matter how badly we ruin this, it won't stop us from further corruptions.
... Still more to look over. I just want to get this bit of information out and relax from trying to analyze what's here, for a time. Whatever the exact details may be, 007C looks like a small timer, 00D1 looks like a single byte incrementing frame timer, and the thing we really want to reach is 01BD as that's where our camera position is, something we might have full control over.
Editor, Experienced Forum User, Skilled player (1098)
Joined: 9/27/2008
Posts: 1075
Okay, I have a small plan now. It isn't much, but hopefully the setup gets the game executing stuff we want. 0064 - Place a Mass Transit tile here. Some of the graphics will likely get screwy. Just a sign things are working! Well, if we leave this alone, we get a long jump right back to before this spot, giving us an undesired loop we won't escape. Placing 000072 as our destination at least lets us jump ahead a little. We can also place something at 0062 instead for what is more likely a bigger graphical mess. 007C - Some kind of timer. Its speed is adjusted by whether you have buttons pressed. Might be helpful to keep in mind, although its range of possible values is limited. 0091 - If this stays 0x40, we may get an RTI, which could easily put a stop to our plans. It looks safe to corrupt, as it's related to menus, and unused in build mode, so placing roads on 0090 means we avoid this mess. 00A9,2x - One of the stack addresses. 00AB,2x - The other stack address. 00AA - Once we're ready to break the game, we place something here. Probably a Power Line or something. 00C7 - Looks "random". I hope it doesn't get us into trouble after carefully aligning the timers. 00D1 - A frame timer. Counts up to FF, then wraps back to 00 to start over again. I'm not sure if we can corrupt 00D2, but if it sticks, it shouldn't cause problems. An easy jump ahead by 0032 would get us past a few addresses, and make it easier to reach 012D, although whether the timer lines up nicely with what we're doing is another question. 012D - Frame timers are here! Well, there's one at 012B, but it's used for something else and will not have the values we need. However, 012D, 012F, and 0131 are each two-byte timers that tick down every frame. It should be pretty safe to corrupt them, then wait for the right instructions to turn up. A string of bytes like 5C 98 A5 7F would produce JMP $7FA598, right into our power map stat, which we have almost complete control over with normal gameplay. We might prefer an address a dozen or so bytes after, as we can control the power in the middle of the map better than the top edge only. 01BD - Our camera takes up the four bytes at this location. It's notably farther away than 012D, but it is the alternative thing to try to give ourselves the arbitrary jump we might like. I haven't analyzed the stuff between 012D and here, though, as I'm more attracted to the idea of making the timers work. These are the most interesting notes that come to mind at the moment. The more I look at this RAM, the more optimistic I feel that ACE is possible. There's a lot of 00 bytes, which are safe thanks to the BRK instruction giving us an RTI bouncing us back to where we were. And yet, through all this, I haven't actually asked about the exact process of moving the cursor and camera to allow for overwriting these addresses. Probably just as simple as going off the top edge or left edge and placing something in that black gunk, but I figure I should ask anyway. Exactly where does the cursor end up relative to the top-left corner of the map for this sort of targeted corruption?
Active player, Experienced Forum User (344)
Joined: 10/4/2015
Posts: 86
It'll probably take me a few days to find some time to sit down with it and step through line by line of the execution. I guess I'll set a breakpoint on reading 0xAA and try to follow it from there. It seems like we have all the tools we need to get a really cool ACE, as long as we figure out the bootstrap step.
Post subject: Oh, right. The pass time step.
Editor, Experienced Forum User, Skilled player (1098)
Joined: 9/27/2008
Posts: 1075
Okay, plan of attack: * Start game in any difficulty, it shouldn't matter too much (although Hard would be tight on cash) * Build rows of power lines. We are writing a bootstrap code, with each tile representing one bit. * Build power plant, maybe two, to power these lines we just placed * Wait long enough for the game to calculate the power * Build Mass Transit (rails) tile on 0064 (206 tiles left of the normal top-left corner) * Build Roads on 0090 (184 tiles left of that same corner) * Spam-build over the timers at 012C (106 tiles left), 12E (105), and 12F (104) for great justice * Build Roads on 00AA * ACE achieved. Now press buttons several thousand times faster than normal humans. Well, I'm hoping this plan is simple and effective. It should, at least, give something concrete to try out as opposed to simply hand waving possible ideas around. About the spam-building for great justice step: There are a few two-byte timers we can corrupt and abuse to our liking. Whenever we build over one of these, we set the upper byte of one timer to one of three values (0x32, 0x62, or 0x72, depending on tool) and zero out the lower byte of another. The timers tick down every frame, so zeroing out the low byte will effectively decrement the upper byte. We can simply zero out the low byte multiple times to adjust the high byte as needed, and pick a different timing on when we zero out that low byte for the last time to line up the values of different timers. I took a look at moving the cursor off the top edge of the map. Judging from where it's corrupting stuff, it's probably just taking the effective Y position you would be building on, modulo 256. This would modify 7FF110 for going one tile up, and will never reach the 7E0000 region we want to modify. I'm thinking we're stuck with moving the cursor to the left a ways. As the width of the map is 120 tiles, and we are guaranteed to be able to have two functioning rows of power lines for purposes of ACE, we can get ourselves 30 easy bytes of bootstrap code in the power array. If it's necessary, we can probably get more bytes by adding more rows, but we start to lose precision control on how we set the individual bits as we need some continuous connection to every power line. The more 1 bits in your code, the less of a problem this would be, though. On the other hand, 30 bytes of bootstrapping with absolute control over every byte of this 30 really should be a dream compared to getting an ACE in something like Super Mario World. But once we hit ACE, what then? Fix the stack, and any critical memory we ruined along the way, then run the 600k population message routine? Well, that would seem like a really basic way to achieve the "ending". Use it as a shortcut to glitch our money? But then we're not actually using the full power to get to the "ending" as soon as possible. Something... Interesting? A bit outside what I'd do myself, but snap, it would be awesome if someone has ideas and can execute those. I, for one, would like to see someone bulldoze a tornado. Or the plane, it keeps crashing on nice buildings even on Easy. In any case, I feel as though I have 99% confirmation that ACE is possible. My method was basically running a debugger and doing direct memory edits on spots we should be able to corrupt as the Program Counter was getting close. To my knowledge, the spots I wrote over do not refresh so long as you don't enter any menus, and apparently don't have critical function for reads, apart from the final step of corrupting the stack. Finally, we have a LOT of room in the 7F bank. Well, a lot of it is dedicated to the 120x100 building area, but after that is the History stats and the various map stats. Since we should have ACE, we can simply prevent the game from ever recalculating what the stats should be, possibly by convincing it the calculation was already done.
Active player, Experienced Forum User (344)
Joined: 10/4/2015
Posts: 86
I don't really have good ideas for arbitrary things. I guess it might be funny to build a city full of TOPs instantaneously, or trigger Monster Attacks, or update the graphics to look like Sim City 2000... But I'm a fan of just proof-of-concept jumping to the routine to the 600k message popup. That way no waiting is needed. My creativity is limited : )
Active player, Experienced Forum User (344)
Joined: 10/4/2015
Posts: 86
Just to post an update: I have confirmed that indeed ACE is possible. I've gotten code execution at 0x0, and written instructions carefully over bytes which would have otherwise jumped to useless parts of code, etc. I now have it executing at 0x0120, which is the timer section. I'm currently trying to figure out what part of the code executes the "Awesome Mayor" screen. This is my first ACE run so I'm happy to just get a working PoC, instead of anything too showy. (At least at first. Once I have the PoC, I'm open to suggestions of alternate payloads). Do you think the ACE needs to return to normal gameplay, or is it ok if I just leave the game in a broken state? Edit: For the curious, we are blessed because 0x72 is a 2-byte opcode, and 0x62 is a 3-byte opcode. This means 62 can be used to skip over an extra byte, if clobbering it would have broken the game.
crazygerry
He/Him
Joined: 8/23/2018
Posts: 1
Dammit wrote:
FatRatKnight wrote:
lua script that gives the exact land value of each square
I'll try. I'll edit later with the result. Edit: OK, seems to be working. As usual, I borrowed bits from scripts by Dromecious and others. http://lua.pastey.net/108707 Hold L to see the values and R to see the color grid. The colors match the ones in the in-game land value map, but I had to guess the values where the gradients are. I scaled the values to percent because three digits won't fit in a 16x16 box.
I added mouse support for a better game experience
print("SimCity grid viewer, for snes9x-rr 1.43 ~ 1.51")
print("written by Dammit, 2/21/2009 ~ 6/2/2011") --dammit9x at hotmail dot com
print("mouse support by crazygerry 2018")
print("tested with: https://github.com/gocha/snes9x/ 1.53 ~ 1.55")
--(old) use with: http://code.google.com/p/snes9x-rr/
--purpose: display land values and other parameters on the playfield for the SimCity game
--discussion: http://tasvideos.org/forum/viewtopic.php?p=192582#192582

local hotkey = { --hotkey settings
	mode_inc    = {"K", "cycle view modes forward (or middleclick)"},
	mode_dec    = {"L", "cycle view modes backward"},
	show_values = {"U", "show/hide grid values"},
	show_grid   = {"I", "show/hide the grid"},
	show_coords = {"O", "show/hide the map coordinates and city center"},
	num_format  = {"P", "switch between decimal & hex numbers"},
}

local globals = { --initial settings
	view_mode   = 1,
	show_values = true,
	show_grid   = true,
	show_coords = false,
	hex_numbers = false,
	use_mouse = true,
}

local label_x, label_y = 128, 216 --where to draw the view mode label
local coord_x, coord_y = 216, 216 --where to draw the map coordinates

local color = {
	["level 8"]     = 0xFF0000, --these are the in-game map colors
	["level 7"]     = 0xFF6300,
	["level 6"]     = 0xFFB500,
	["level 5"]     = 0xFFFF00,
	["level 4"]     = 0x00FF00,
	["level 3"]     = 0x00BD00,
	["level 2"]     = 0x008C00,
	["level 1"]     = 0x005A00,
	["powered"]     = 0xFF8400,
	["unpowered"]   = 0x00B500,
	["city center"] = 0xFF00FF,
	["dec text"]    = 0xFFFFFF, --grid values in decimal mode
	["hex text"]    = 0xFFFF00, --grid values in hexadecimal mode
	["label text"]  = 0x00FF00, --view mode label text
	["coord text"]  = 0x00FFFF, --coordinate text
}

local opacity = {
	fill    = 0x20, --inside of boxes
	outline = 0xA0, --outside of boxes
	text    = 0xFF,
}

--------------------------------------------------------------------------------

--prepare color settings
local fill, outline = {}, {}
local function map_colors(index, name)
	fill[index]    = bit.lshift(color[name], 8) + opacity.fill
	outline[index] = bit.lshift(color[name], 8) + opacity.outline
end

for i = 0xFF, 0, -1 do
	if i > 0xE0 then map_colors(i, "level 8")
	elseif i > 0xC0 then map_colors(i, "level 7")
	elseif i > 0xA0 then map_colors(i, "level 6")
	elseif i > 0x80 then map_colors(i, "level 5")
	elseif i > 0x60 then map_colors(i, "level 4")
	elseif i > 0x40 then map_colors(i, "level 3")
	elseif i > 0x20 then map_colors(i, "level 2")
	elseif i > 0x00 then map_colors(i, "level 1")
	else
		fill[i]    = 0 --blank/transparent
		outline[i] = 0
	end
end
map_colors(true, "powered")
map_colors(false, "unpowered")
map_colors("city center", "city center")
fill["dec text"]   = bit.lshift(color["dec text"], 8) + opacity.text
fill["hex text"]   = bit.lshift(color["hex text"], 8) + opacity.text
fill["label text"] = bit.lshift(color["label text"], 8) + opacity.text
fill["coord text"] = bit.lshift(color["coord text"], 8) + opacity.text

--memory addresses
local address = {
	x_tile   = 0x7E01BD, --camera position by upper-left tile
	y_tile   = 0x7E01BF,
	x_center = 0x7E0BA9, --coordinate of the city center
	y_center = 0x7E0BAA,
	playing  = 0x7E0012, --a game is in progress: draw coords
	hud      = 0x7E2210, --in-game HUD is shown: don't draw grid
	dark     = 0x7E90FF, --darkened BG: don't draw grid
	magnify  = 0x7E019B, --magnifier tool is open: draw grid
	time     = 0x7E0B51, --increments four times per game month; currently unused
	x_cursor = 0x7E01EB,
	y_cursor = 0x7E01ED,
	taxind   = 0x7E2016, -- holds some values to identify tax screen
}

--parameter values are stored here
local view = {
	{address = 0x7F6B00, sqsize = 2, bytes = 1, name = "Land value"},
	{address = 0x7F76B8, sqsize = 2, bytes = 1, name = "Crime"},
	{address = 0x7F8270, sqsize = 2, bytes = 1, name = "Pollution"},
	{address = 0x7F8E28, sqsize = 2, bytes = 1, name = "Population density"},
	{address = 0x7F99E0, sqsize = 2, bytes = 1, name = "Traffic"},
	{address = 0x7FA598, sqsize = 1, bytes = 1/8, name = "Power"},
	{address = 0x7FAB74, sqsize = 4, bytes = 1, name = "Land value modifier"},
	{address = 0x7FAE62, sqsize = 8, bytes = 2, name = "Growth rate"},
	{address = 0x7FAFE8, sqsize = 8, bytes = 1, name = "Police coverage"},
	{address = 0x7FB0AB, sqsize = 8, bytes = 1, name = "Fire coverage"},
}

local screenwidth, screenheight, tilesize = 256, 224, 8 --size in pixels
local mapwidth, mapheight = 120, 100 --size in tiles
local pressing_old = {} --array for keyboard input

for _, mode in ipairs(view) do
	mode.y_step = mapwidth / mode.sqsize
	mode.read = mode.bytes == 2 and memory.readwordsigned or memory.readbyte
end

print()
print("To enable the grid, hide the HUD with button Y or open the magnifier.")
print()
for _, v in pairs(hotkey) do
	print("Press the '" .. v[1] .. "' key to " .. v[2] .. ".")
end

function bit(p) --http://lua-users.org/wiki/BitwiseOperators
	return 2 ^ (p - 1)
end

function hasbit(x, p)
	return x % (p + p) >= p
end

local function draw_boxes()
	local x_tile, y_tile, mode = globals.x_tile, globals.y_tile, view[globals.view_mode]
	local y = 0
	while y < screenheight do
		local y_next = y + mode.sqsize * tilesize
		if y == 0 then
			y_next = (mode.sqsize - y_tile % mode.sqsize) * tilesize
		end
		if y_next > screenheight then
			y_next = screenheight
		end
		local x = 0
		while x < screenwidth do
			local x_next = x + mode.sqsize * tilesize
			if x == 0 then
				x_next = (mode.sqsize - x_tile % mode.sqsize) * tilesize
			end
			if x_next > screenwidth then
				x_next = screenwidth
			end
			if x_tile + x/tilesize >= 0 and y_tile + y/tilesize >= 0 --check the map boundaries
			and x_tile + x/tilesize < mapwidth and y_tile + y/tilesize < mapheight then
				local value = mode.read(mode.address + math.floor(
				(math.floor((x_tile + x/tilesize) / mode.sqsize)
				+ math.floor((y_tile + y/tilesize) / mode.sqsize) * mode.y_step) * mode.bytes))

				if globals.show_grid then
					if mode.read == memory.readwordsigned then
						if value > 0xFF then value = 0xFF end
						if value < -0xFF then value = -0xFF end
						value = math.floor((value + 0xFF) / 2)
					end
					if mode.bytes < 1 then
						value = hasbit(value, bit(8 - (x_tile + x/tilesize + (y_tile + y/tilesize) * mode.y_step) % 8))
					end
					gui.box(x, y, x_next-1, y_next-1, fill[value], outline[value])
				end

				if globals.show_values and value ~= 0 and mode.sqsize > 1 and
					x_next >= 2 * tilesize and y_next >= 2 * tilesize and
					x + 2 * tilesize <= screenwidth and y + 2 * tilesize <= screenheight then
					local color
					if globals.hex_numbers then
						value, color = string.format("%02X", value), fill["hex text"]
					else
						value, color = string.format("%d", value), fill["dec text"]
					end
					gui.text(x_next - value:len() * 4 - 1, y + 1, value, color)
				end

			end
			x = x_next
		end
		y = y_next
	end

	if globals.show_grid or globals.show_values then --show the label only with the grid or values active
		gui.text(label_x, label_y, mode.name, fill["label text"])
	end
end

local function draw_data()
	local x_tile, y_tile, x_center, y_center = globals.x_tile, globals.y_tile, globals.x_center, globals.y_center
	if globals.grid_ok then
		draw_boxes()
	end

	if globals.show_coords then --show the coordinates & city center
		if globals.use_mouse == false then
			gui.text(coord_x, coord_y, "(" .. x_tile .. ", " .. y_tile .. ")", fill["coord text"])
		end

		local xdraw, ydraw = (x_center - x_tile) * tilesize, (y_center - y_tile) * tilesize
		if x_center < x_tile then
			xdraw = 0
		end
		if x_center >= x_tile + screenwidth/tilesize then
			xdraw = screenwidth - tilesize
		end
		if y_center < y_tile then
			ydraw = 0
		end
		if y_center >= y_tile + screenheight/tilesize then
			ydraw = screenheight - tilesize
		end
		gui.box(xdraw, ydraw, xdraw + 7, ydraw + 7, fill["city center"], outline["city center"])
	end
end

local function mouse_support(inp)
	local taxscreenindicatorvalue = memory.readbyte(address.taxind)
	local taxscreenindicatorvalues = {131,133,135,137,139,141,195,197,199,201}
	local taxscreenvisible, movemap = false, false
	local pad = joypad.get(1)
	local x_mousepos, y_mousepos = math.floor(inp.xmouse/8), math.floor(inp.ymouse/8)
	local x_mouseonmap, y_mouseonmap = x_mousepos + memory.readbytesigned(address.x_tile), y_mousepos + memory.readbytesigned(address.y_tile)
	for i = 1, 10 do
		if taxscreenindicatorvalues[i] == taxscreenindicatorvalue then taxscreenvisible = true break end
	end
	if taxscreenvisible == false then	
		if(x_mouseonmap >= 0 and x_mouseonmap < mapwidth and y_mouseonmap >= 0 and y_mouseonmap < mapheight) then
			gui.text(coord_x, coord_y, "(" .. x_mouseonmap+1 .. ", " .. y_mouseonmap+1 .. ")", fill["coord text"])		
		end
		if(x_mouseonmap >= -6 and x_mouseonmap < mapwidth+6 and y_mouseonmap >= -6 and y_mouseonmap < mapheight+6) then
			memory.writebyte(address.x_cursor,x_mousepos*8)
			memory.writebyte(address.y_cursor,y_mousepos*8)			
		end
		if(inp.xmouse > 232) then memory.writebyte(address.x_cursor,232) end
		if(inp.xmouse < 16) then memory.writebyte(address.x_cursor,16) end
		if(inp.ymouse > 192) then memory.writebyte(address.y_cursor,192) end
		if(inp.ymouse < 24) then memory.writebyte(address.y_cursor,24) end
	end
	if (inp.xmouse >= screenwidth-1) then movemap, pad.right = true, true joypad.set(1,pad) end
	if (inp.xmouse <= 0) then movemap, pad.left = true, true joypad.set(1,pad) end
	if (inp.ymouse >= screenheight-1) then movemap, pad.down = true, true joypad.set(1,pad) end
	if (inp.ymouse <= 0) then movemap, pad.up = true, true joypad.set(1,pad) end
	if (movemap and inp.leftclick == false) then pad.Y = true end
	if (inp.leftclick) then pad.B = true joypad.set(1,pad) end
	if (inp.rightclick) then pad.X = true joypad.set(1,pad) end
end

emu.registerafter(function()
	globals.game_playing = memory.readbyte(address.playing) > 0
	if not globals.game_playing then
		pressing_old = {}
		return
	end

	globals.grid_ok = memory.readbyte(address.dark) == 0 and 
		(memory.readword(address.hud) == 0x5555 or memory.readbyte(address.magnify) > 0)
	globals.x_tile, globals.y_tile = memory.readbytesigned(address.x_tile), memory.readbytesigned(address.y_tile)
	globals.x_center, globals.y_center = memory.readbyte(address.x_center), memory.readbyte(address.y_center)
	
	local pressing = input.get()
	
	if (globals.show_grid or globals.show_values) and globals.grid_ok then
		if (pressing[hotkey.mode_inc[1]] or pressing.middleclick) and not (pressing_old[hotkey.mode_inc[1]] or pressing_old.middleclick) then
			globals.view_mode = globals.view_mode >= #view and 1 or globals.view_mode + 1
		elseif pressing[hotkey.mode_dec[1]] and not pressing_old[hotkey.mode_dec[1]] then
			globals.view_mode = globals.view_mode == 1 and #view or globals.view_mode - 1
		end
	end
	if pressing[hotkey.show_values[1]] and not pressing_old[hotkey.show_values[1]] and globals.grid_ok then
		globals.show_values = not globals.show_values
	end
	if pressing[hotkey.show_grid[1]] and not pressing_old[hotkey.show_grid[1]] and globals.grid_ok then
		globals.show_grid = not globals.show_grid
	end
	if pressing[hotkey.show_coords[1]] and not pressing_old[hotkey.show_coords[1]] then
		globals.show_coords = not globals.show_coords
	end
	if pressing[hotkey.num_format[1]] and not pressing_old[hotkey.num_format[1]] and globals.show_values then
		globals.hex_numbers = not globals.hex_numbers
	end

	pressing_old = pressing
end)

gui.register(function()
	if globals.game_playing then
		draw_data()
		if globals.use_mouse then
			mouse_support(input.get())
		end
	end
end)
Have fun !
1 2
10 11