display-name: ["Games/King's Quest", "Games/King's Quest II: Romancing the Throne", "Games/The Black Cauldron", "Games/Donald Duck's Playground",
	       "Games/King's Quest III: To Heir Is Human", "Games/Space Quest: The Sarien Encounter", "Games/Leisure Suit Larry in the Land of the Lounge Lizards",
	       "Games/Mixed-Up Mother Goose", "Games/Police Quest: In Pursuit of the Death Angel", "Games/Space Quest II: Vohaul's Revenge",
	       "Games/Gold Rush!", "Games/Manhunter: New York", "Games/King's Quest IV: The Perils of Rosella", "Games/Manhunter 2: San Francisco"]

meta: 
{
	"Working": "Backgrounds, sprites and sounds",
	"Partly working": "Scripts - issues with gotos and strings aren't pushed into print() yet",
	"Thanks to" : "The ScummVM team"
}

nice-names:
{
	"logdir":    "script",
	"picdir":    "backdrop",
	"viewdir":   "sprite",
	"snddir":    "sound",
	"logdir3":   "script",
	"picdir3":   "backdrop",
	"viewdir3":  "sprite",
	"snddir3":   "sound"
}

//AGI 2

if (file.name like ["logdir", "picdir", "viewdir", "snddir"])
{
	index-name: file.name
	file-number: 0

	file while (file.remaining-bytes > 0)
	{
		agi-version: 2

		unsigned24be directory-item

		skip-if (directory-item == 0xFFFFFF)
	
		filename: "vol." + (directory-item >> 20)

		offset-including-header: directory-item & 0xFFFFF
		offset: offset-including-header + 5
	
		name: nice-names[file.name] + file-number
	
		//KQ1 has oob files...?
		from filename at 0
		{
			if (offset-including-header < file.size)
			{
				at offset-including-header
				{
					unsigned16 signature
					unsigned8 volumeID
					unsigned16 size			
				}
			}
			else 
			{
				signature: 0
				volumeID: 0
				size: 0
			}

			file-number = file-number + 1
		}
	}
}

if (file.name like "*.ovl")
{
	folder: "Code overlays"
}

agi3names:
{
	1:		"logdir3",
	2:		"picdir3",
	3:		"viewdir3",
	4:		"snddir3"
}


if ((file.name like "??dir") or (file.name like "???dir"))
{
	file-number: 1
	volIndexName: file.name

	gameID: ""

	i: 0
	loop (file.name.length - 3)
	{
		gameID = gameID + file.name[i]
		i = i + 1
	}

	file [4]
	{
		name: agi3names[file-number]
		unsigned16 offset
		file-number = file-number + 1
	}
}

if (file.name like ["logdir3", "picdir3", "viewdir3", "snddir3"])
{
	indexName: name
	indexNameVol: volIndexName

	fileNumber: 0

	file while (file.remaining-bytes > 0)
	{
		agi-version: 3

		fileNumber = fileNumber + 1

		unsigned24be directoryItem
		skip-if (directoryItem == 0xFFFFFF)
	
		filename: gameID + "vol." + (directoryItem >> 20)
		
		//UGH, my mh2 doesn't have vol.7 so this whole file fails to unpack
		skip-if ( ! ( exists (filename) ) ) 

		offsetIncludingHeader: directoryItem & 0xFFFFF
		offset: offsetIncludingHeader + 7

		name: nice-names[file.name] + fileNumber

		from filename at offsetIncludingHeader
		{
			unsigned16 signature
			unsigned8 volumeID
			unsigned16 decompressed-size
			unsigned16 size	

			if ((signature == 41585) or (signature == 49724)) //NOT sound (?)
			{
				name = "unknown.audio." + fileNumber
			}	

			if ((size != decompressed-size) and (indexName != "picdir3") and (signature != 41585) and (signature != 49724))
			{
				lz-reset: 256
				lz-eof: 257
				lz-start: 258	
				lz-precode: 0		
				lz-maxbits: 11	
				lz-readbits: 24
				lz-resetfull: true
				lz-readmethod: "agi"
				compressed "LZW"
			}
			AGI3: true
		}
		
	}
}

// Common AGI

if (file.name like "sprite*")
{
	unsigned8 unknown //always 1 or 2
	unsigned8 unknown //always 1

	unsigned8 loopCount

	unsigned16 descriptionOffset


	image-layout-width: 320
	
	image-layout 
	{
		loop (loopCount)
		{
			unsigned16 loopOffset

			at loopOffset
			{
				startOfLoop: file.position
				unsigned8 frameCount

				loop (frameCount)
				{
					unsigned16 frameOffset

					at startOfLoop + frameOffset
					{
						unsigned8 width-in-bytes
						unsigned8 height

						width: width-in-bytes * 2

						unsigned8 transparencyMirroring //we only care about transparency

						compressed [file.remaining-bytes] "AGI-ViewCel"
						{				
							image	
							{	
								format: "I4"						
								transparent-index: transparencyMirroring & 0xF							
							}
						}
					}
				}
			}
		}
	} 
}

if (file.name like "backdrop*")
{
	//interpret-as "image-AGI"
	compressed [file.size] "image-AGI"
	{
		image
		{
			width: 320
            height: 400
            format: "I4"
		}
	}
}

asciiTable: {
   32: " ",
   39: "'",
   45: "-",
   46: ".",
   65: "A", 
   66: "B",  
   67: "C",  
   68: "D",  
   69: "E",  
   70: "F",  
   71: "G",  
   72: "H",   
   73: "I",  
   74: "J",   
   75: "K",   
   76: "L",   
   77: "M",   
   78: "N",   
   79: "O",   
   80: "P",  
   81: "Q",  
   82: "R",  
   83: "S",  
   84: "T",  
   85: "U",  
   86: "V",  
   87: "W",   
   88: "X",  
   89: "Y",   
   90: "Z",
   97: "a",
  98: "b",
  99: "c",
 100: "d",
 101: "e",
 102: "f",
 103: "g",
 104: "h",
 105: "i",
 106: "j",
 107: "k",
 108: "l",
 109: "m",
 110: "n",
 111: "o",
 112: "p",
 113: "q",
 114: "r",
 115: "s",
 116: "t",
 117: "u",
 118: "v",
 119: "w",
 120: "x",
 121: "y",
 122: "z" }

if (file.name == "words.tok")
{
	previousString: ""

	unsigned16be[26] offsets
	loop while (file.remaining-bytes > 1)
	{
		thisString: ""

		unsigned8 previousCount

		i: 0
		loop (previousCount)
		{
			thisString = thisString + previousString[i]
			i = i + 1
		}
		
		loop
		{
			unsigned8 char
			decryptedChar: char & 0x7F
			decryptedChar: decryptedChar ^ 0x7F

			thisString: thisString + asciiTable[decryptedChar]
			
		} while ((char & 0x80) == 0)

		unsigned16be wordNum

		text
		{
			string-data: thisString + ": " + wordNum +"\r\n"
		}

		previousString = thisString
	}
}

if (file.name == "object")
{
	folder: "Text"
	
	avis-offset: 0
	encrypted [file.size] "agi-avis"
	{
		unsigned16 objectNamesStart
		unsigned8 maxAnimatedObjects

		loop(objectNamesStart / 3)
		{
			unsigned16 textOffset
			unsigned8 startRoom

			at textOffset + 3
			{
				string objectName
				text
				{
					string-data: objectName + "\r\n"
				}
			}
		}
	}
}

if (file.name like "script*")
{
	interpret-as "agi-script"
}

agi-opcodes-condition:
{
	0x01: {"name": "equaln", "args": 2},
	0x02: {"name": "equalv", "args": 2},
	0x03: {"name": "lessn", "args": 2},
	0x04: {"name": "lessv", "args": 2},
	0x05: {"name": "greatern", "args": 2},
	0x06: {"name": "greaterv", "args": 2},
	0x07: {"name": "isset", "args": 1},
	0x08: {"name": "issetv", "args": 1},
	0x09: {"name": "has", "args": 1},
	0x0A: {"name": "obj.in.room", "args": 2},
	0x0B: {"name": "posn", "args": 5},
	0x0C: {"name": "controller", "args": 1},
	0x0D: {"name": "have.key", "args": 0},
	0x0E: {"name": "said", "args": -1}, //custom opcode length
	0x0F: {"name": "compare.strings", "args": 2},
	0x10: {"name": "obj.in.box", "args": 5},
	0x11: {"name": "center.posn", "args": 5},
	0x12: {"name": "right.posn", "args": 5}
}

agi-opcodes-action:
{
	0x00:{"name": "return", "args":	0},
	0x01:{"name": "increment", "args": 1},
	0x02:{"name": "decrement", "args": 1},
	0x03:{"name": "assignn", "args": 2},
	0x04:{"name": "assignv", "args": 2},
	0x05:{"name": "addn", "args": 2},
	0x06:{"name": "addv", "args":			2},
	0x07:{"name": "subn", "args":			2},
	0x08:{"name": "subv", "args":			2},
	0x09:{"name": "lindirectv", "args":	2},
	0x0A:{"name": "rindirect", "args":	2},
	0x0B:{"name": "lindirectn", "args":	2},
	0x0C:{"name": "set", "args":		1},
	0x0D:{"name": "reset", "args":		1},
	0x0E:{"name": "toggle", "args":		1},
	0x0F:{"name": "set.v", "args":		1},
	0x10:{"name": "reset.v", "args":		1},
	0x11:{"name": "toggle.v", "args":		1},
	0x12:{"name": "new.room", "args":		1},
	0x13:{"name": "new.room.v", "args":	1},
	0x14:{"name": "load.logics", "args":	1},
	0x15:{"name": "load.logics.v", "args":	1},
	0x16:{"name": "call", "args":			1},
	0x17:{"name": "call.v", "args":		1},
	0x18:{"name": "load.pic", "args":		1},
	0x19:{"name": "draw.pic", "args":		1},
	0x1A:{"name": "show.pic", "args":		0},
	0x1B:{"name": "discard.pic", "args":	1},
	0x1C:{"name": "overlay.pic", "args":	1},
	0x1D:{"name": "show.pri.screen", "args":	0},
	0x1E:{"name": "load.view", "args":	1},
	0x1F:{"name": "load.view.v", "args":	1},
	0x20:{"name": "discard.view", "args":	1},
	0x21:{"name": "animate.obj", "args":	1},
	0x22:{"name": "unanimate.all", "args":	0},
	0x23:{"name": "draw", "args":			1},
	0x24:{"name": "erase", "args":		1},
	0x25:{"name": "position", "args":		3},
	0x26:{"name": "position.v", "args":	3},
	0x27:{"name": "get.posn", "args":		3},
	0x28:{"name": "reposition", "args":	3},
	0x29:{"name": "set.view", "args":		2},
	0x2A:{"name": "set.view.v", "args":	2},
	0x2B:{"name": "set.loop", "args":		2},
	0x2C:{"name": "set.loop.v", "args":	2},
	0x2D:{"name": "fix.loop", "args":		1},
	0x2E:{"name": "release.loop", "args":	1},
	0x2F:{"name": "set.cel", "args":		2},
	0x30:{"name": "set.cel.v", "args":	2},
	0x31:{"name": "last.cel", "args":		2},
	0x32:{"name": "current.cel", "args":	2},
	0x33:{"name": "current.loop", "args":	2},
	0x34:{"name": "current.view", "args":	2},
	0x35:{"name": "number.of.loops", "args":	2},
	0x36:{"name": "set.priority", "args":	2},
	0x37:{"name": "set.priority.v", "args":	2},
	0x38:{"name": "release.priority", "args":	1},
	0x39:{"name": "get.priority", "args":	2},
	0x3A:{"name": "stop.update", "args":	1},
	0x3B:{"name": "start.update", "args":	1},
	0x3C:{"name": "force.update", "args":	1},
	0x3D:{"name": "ignore.horizon", "args":	1},
	0x3E:{"name": "observe.horizon", "args":	1},
	0x3F:{"name": "set.horizon", "args":	1},
	0x40:{"name": "object.on.water", "args":	1},
	0x41:{"name": "object.on.land", "args":	1},
	0x42:{"name": "object.on.anything", "args":	1},
	0x43:{"name": "ignore.objs", "args":	1},
	0x44:{"name": "observe.objs", "args":	1},
	0x45:{"name": "distance", "args":	3},
	0x46:{"name": "stop.cycling", "args":	1},
	0x47:{"name": "start.cycling", "args":	1},
	0x48:{"name": "normal.cycle", "args":	1},
	0x49:{"name": "end.of.loop", "args":	2},
	0x4A:{"name": "reverse.cycle", "args":	1},
	0x4B:{"name": "reverse.loop", "args":	2},
	0x4C:{"name": "cycle.time", "args":	2},
	0x4D:{"name": "stop.motion", "args":	1},
	0x4E:{"name": "start.motion", "args":	1},
	0x4F:{"name": "step.size", "args":	2},
	0x50:{"name": "step.time", "args":	2},
	0x51:{"name": "move.obj", "args":	5},
	0x52:{"name": "move.obj.v", "args":	5},
	0x53:{"name": "follow.ego", "args":	3},
	0x54:{"name": "wander", "args":	1},
	0x55:{"name": "normal.motion", "args":	1},
	0x56:{"name": "set.dir", "args":	2},
	0x57:{"name": "get.dir", "args":	2},
	0x58:{"name": "ignore.blocks", "args":	1},
	0x59:{"name": "observe.blocks", "args":	1},
	0x5A:{"name": "block", "args":	4},
	0x5B:{"name": "unblock", "args":	0},	 	 	 	 	 	 	 
	0x5C:{"name": "get", "args":	1},
	0x5D:{"name": "get.v", "args":	1},
	0x5E:{"name": "drop", "args":	1},
	0x5F:{"name": "put", "args":	2},
	0x60:{"name": "put.v", "args":	2},
	0x61:{"name": "get.room.v", "args":	2},
	0x62:{"name": "load.sound", "args":	1},
	0x63:{"name": "sound", "args":	2},
	0x64:{"name": "stop.sound", "args":	0},	 	 	 	 	 	 	 
	0x65:{"name": "print", "args":	1},
	0x66:{"name": "print.v", "args":	1},
	0x67:{"name": "display", "args":	3},
	0x68:{"name": "display.v", "args":	3},
	0x69:{"name": "clear.lines", "args":	3},
	0x6A:{"name": "text.screen", "args":	0},
	0x6B:{"name": "graphics", "args":	0},
	0x6C:{"name": "set.cursor.char", "args":	1},
	0x6D:{"name": "set.text.attribute", "args":	2},
	0x6E:{"name": "shake.screen", "args":	1},	 	 	 	 	 	 
	0x6F:{"name": "configure.screen", "args": 3},
	0x70:{"name": "status.line.on", "args":	0},	 	 	 	 	 	 	 
	0x71:{"name": "status.line.off", "args":	0},
	0x72:{"name": "set.string", "args":	2},
	0x73:{"name": "get.string", "args":	2},
	0x74:{"name": "word.to.string", "args":	2},
	0x75:{"name": "parse", "args":	1},
	0x76:{"name": "get.num", "args":	2},
	0x77:{"name": "prevent.input", "args":	0},
	0x78:{"name": "accept.input", "args":	0},
	0x79:{"name": "set.key", "args":	3},
	0x7A:{"name": "add.to.pic", "args":	7},
	0x7B:{"name": "add.to.pic.v", "args":	7},
	0x7C:{"name": "status", "args":	0},	 	 	 	 	 	 	 
	0x7D:{"name": "save.game", "args":	0},	 	 	 	 	 	 	 
	0x7E:{"name": "restore.game", "args":	0},	 	 	 	 	 	 	 
	0x7F:{"name": "init.disk", "args":	0},	 	 	 	 	 	 	 
	0x80:{"name": "restart.game", "args":	0},
	0x81:{"name": "show.obj", "args":	1},
	0x82:{"name": "random", "args":	3},
	0x83:{"name": "program.control", "args":	0},	 	 	 	 	 	 	 
	0x84:{"name": "player.control", "args":	0},	 	 	 	 	 	 	 
	0x85:{"name": "obj.status.v", "args":	1},
	0x86:{"name": "quit", "args":	1},
	0x87:{"name": "show.mem", "args":	0},
	0x88:{"name": "pause", "args":	0},
	0x89:{"name": "echo.line", "args":	0},
	0x8A:{"name": "cancel.line", "args":	0},
	0x8B:{"name": "init.joy", "args":	0},
	0x8C:{"name": "toggle.monitor", "args":	0},
	0x8D:{"name": "version", "args":	0},
	0x8E:{"name": "script.size", "args":	1},
	0x8F:{"name": "set.game.id", "args":	1},
	0x90:{"name": "log", "args":	1},
	0x91:{"name": "set.scan.start", "args":	0},
	0x92:{"name": "reset.scan.start", "args":	0},
	0x93:{"name": "reposition.to", "args":	3},
	0x94:{"name": "reposition.to.v", "args":	3},
	0x95:{"name": "trace.on", "args":	0},
	0x96:{"name": "trace.info", "args":	3},
	0x97:{"name": "print.at", "args":	4},
	0x98:{"name": "print.at.v", "args":	4},
	0x99:{"name": "discard.view.v", "args":	1},
	0x9A:{"name": "clear.text.rect", "args":	5},
	0x9B:{"name": "set.upper.left", "args":	2},
	0x9C:{"name": "set.menu", "args":	1},
	0x9D:{"name": "set.menu.member", "args":	2},
	0x9E:{"name": "submit.menu", "args":	0},
	0x9F:{"name": "enable.member", "args":	1},
	0xA0:{"name": "disable.member", "args":	1},
	0xA1:{"name": "menu.input", "args":	0},
	0xA2:{"name": "show.obj.v", "args":	1},
	0xA3:{"name": "open.dialogue", "args":	0},
	0xA4:{"name": "close.dialogue", "args":	0},
	0xA5:{"name": "mul.n", "args":	2},
	0xA6:{"name": "mul.v", "args":	2},
	0xA7:{"name": "div.n", "args":	2},
	0xA8:{"name": "div.v", "args":	2},
	0xA9:{"name": "close.window", "args":	0},	 	 	 	 	 	 	 
	0xAA:{"name": "set.simple", "args":	1},
	0xAB:{"name": "push.script", "args":	0},
	0xAC:{"name": "pop.script", "args":	0},
	0xAD:{"name": "hold.key", "args":	0},
	0xAE:{"name": "set.pri.base", "args":	1},
	0xAF:{"name": "discard.sound", "args":	1},
	0xB0:{"name": "hide.mouse", "args":	0}, //0|1	 	 	 	 	 	 	 
	0xB1:{"name": "allow.menu", "args":	1},
	0xB2:{"name": "show.mouse", "args":	0},
	0xB3:{"name": "fence.mouse", "args":	4},
	0xB4:{"name": "mouse.posn", "args":	2},
	0xB5:{"name": "release.key", "args":	0},
	0xB6:{"name": "adj.ego.move.to.xy", "args":	0}
}

file-format agi-script
{
	unsigned16 text-start

	in-ff: false
	in-or: false
	first-in-ff: false
	starts:[]
	lengths:[]
	is-not: false
	elses: []
	results: []
	string-indent-positions: []

	loop while (file.position < text-start)
	{
		unsigned8 this-opcode

		if (this-opcode == 0xFF)
		{
			if (in-ff == true)
			{
				in-ff = false
				unsigned16 method-length
				starts[starts.length] = file.position

				at file.position + method-length - 3
				{
					unsigned8 test
					if (test == 0xFE)
					{
						method-length = method-length - 3
						elses[elses.length] = file.position
					}
				}
				lengths[lengths.length] = method-length
			}
			else
			{
				in-ff = true
				first-in-ff = true
				results[results.length] = "if "
				string-indent-positions[string-indent-positions.length] = file.position - 1
			}
		} 
		else if (this-opcode == 0xFE)
		{
			if ((file.position) like elses)
			{
				results[results.length] = "else"
				string-indent-positions[string-indent-positions.length] = file.position - 1

				unsigned16 method-length

				starts[starts.length] = file.position
				lengths[lengths.length] = method-length
			}
			else
			{
				signed16 method-length

				results[results.length] = "goto " + method-length
				string-indent-positions[string-indent-positions.length] = file.position - 1
			}
		}
		else if (this-opcode == 0xFD)
		{
			is-not = true
		}
		else if (this-opcode == 0xFC)
		{
			in-or = 1 - in-or
		}
		else
		{
			if (in-ff)
			{
				params: "("

				if (this-opcode == 0x0E) //said is special
				{
					unsigned8 oplen
					loop (oplen): i
					{
						unsigned16 param
						if (i != 0)
						{
							params = params + ", "
						}
						params = params + param
					}
					params = params + ")"
				}
				else
				{
					loop (agi-opcodes-condition[this-opcode].args): i
					{
						unsigned8 param
						if (i != 0)
						{
							params = params + ", "
						}
						params = params + param
					}
					
					params = params + ")"
				}

				operand: ""

				if (first-in-ff == true)
				{
					first-in-ff = false
				}
				else
				{
					if (in-or)
					{
						operand = " or "
					}
					else
					{
						operand = " and "
					}
				}

				if (is-not == true)
				{
					operand = operand + "not "
				}

				results[results.length - 1] = results[results.length - 1] + operand + agi-opcodes-condition[this-opcode].name + params
			}
			else
			{
				params: ""
				if (agi-opcodes-action[this-opcode].args != 0)
				{
					params = "("

					loop (agi-opcodes-action[this-opcode].args): i
					{
						unsigned8 param
						if (i != 0)
						{
							params = params + ", "
						}
						params = params + param
					}
					
					params = params + ")"
				}
				
				results[results.length] = agi-opcodes-action[this-opcode].name + params
				string-indent-positions[string-indent-positions.length] = file.position - 1
			}
		}
	}

	text
	{
		string-data: results
		string-indent-pos: string-indent-positions
		indent-starts: starts
		indent-lengths: lengths
	}

	text
	{
		string-data: "\r\n\r\nStrings:\r\n"
	}

	at text-start + 2
	{
		unsigned8 total-message-count
		unsigned16 size

		offsets: []
		indices: []
		message-count: 0

		loop(total-message-count): i
		{
			unsigned16 offset
			if (offset != 0)
			{
				offsets[message-count] = offset + text-start + 2
				indices[message-count] = i
				message-count = message-count + 1
			}
		}

		offsets[message-count] = size + text-start + 2

		loop (message-count): i
		{
			avis-offset: offsets[i] - offsets[0]
			at offsets[i] + 1:

			if (agi-version == 2)
			{
				encrypted[offsets[i+1] - offsets[i]] "agi-avis"
				{	
					string this-text
				}
			}
			else
			{
				string this-text
			}

			text
			{
				string-data: "" + indices[i] + ": " + this-text + "\r\n"
			}
		}
	}
}

function agi-avis
{
	XORData: [0x41, 0x76, 0x69, 0x73, 0x20, 0x44, 0x75, 0x72, 0x67, 0x61, 0x6e ]

	loop (file.size): i
	{
		read unsigned8 encryptedByte
		write unsigned8 (encryptedByte ^ XORData[(i + avis-offset) % 11])
	}
}

function AGI-ViewCel
{
	x: 0
	y: 0
	
	loop (width * height / 2)
	{
		write unsigned8 (transparencyMirroring << 4) + (transparencyMirroring & 0xF)
	}

	loop while (y < height)
	{
		read unsigned8 control
		if (control == 0)
		{
			y = y + 1
			x = 0
		}
		else
		{
			loop (control & 0xF)
			{
				write at ((y * width) + x) / 2 unsigned8 ((control & 0xF0) + (control >> 4))
				x = x + 2
			}
		}
	}
}

if (file.name like "sound*")
{
	if (file.size > 0)
	{
		offsets: [0, 0, 0, 0]
		sizes: [0, 0, 0, 0]

		i: 0
		loop (4)
		{
			unsigned16 offset
			offsets[i] = offset
			i = i + 1
		}

		i = 0
		loop (3)
		{
			sizes[i] = offsets[i + 1] - offsets[i]
		}
		sizes[3] = file.size - offsets[3]

		loop (4): i
		{
			at offsets[i]:

			compressed [sizes[i]] "AGI-sound"
			{
				sound
				{
					channels: 1
					samples-per-second: 44100
					bits-per-sample: 16
				}
			}
		}
	}
}

function AGI-sound
{
	frequency-divisor: 111860

	loop (file.size / 5)
	{
		unsigned16 duration

		unsigned8 thirdByte
		unsigned8 fourthByte
		unsigned8 fifthByte
			
		frequency: ((thirdByte & 0x3F) << 4) + (fourthByte & 0xF)
		frequency-write-time: 44100 / 64 * duration

		write frequency frequency-write-time frequency frequency-divisor (17 * (15 - (fifthByte & 0xF)))
	}
}