Primary Zelda Hacking

Oracles Hacking => Tools => Topic started by: Lin on October 18, 2010, 09:06:02 AM



Title: ZOSE - Zelda Oracles Script Editor
Post by: Lin on October 18, 2010, 09:06:02 AM

~ZOSE is finished. Go to the ZOSE board to ask questions and discuss.~

http://zeldahacking.ulmb.com/index.php?board=25.0 (http://zeldahacking.ulmb.com/index.php?board=25.0)

Welcome to the topic for the 3rd planned tool for Zelda Oracles hacking. This tool will forever change hacking these games as much as ZOLE itself did, and make hacking super easy.

So what is ZOSE? ZOSE is an editor for a scripting language I'm developing that compiles to assembly code. Here will be an example script.
Code:
start 4B 02 ;Start at the default ASM address for the type 2 interaction 4B 02
setwrite 14C000 ;Set it to write out assembly code at a blank location
checktile 46 12 ;Check tile 46 to see if it's 12
retn ;Return if it's not 12
settile 46 13 ;Set tile 46 to 13 if it is
ret ;Return

Simple enough, right? Hopefully it all follows through. Everything has been falling into place, so let's hope it continues that way. I'll have screenshots as soon as possible.

~Lin


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Fatories on October 18, 2010, 06:44:42 PM
 :thumbup:

That is all.


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 21, 2010, 11:06:31 PM
Alright, well this is going to be re-worked a bit. Instead of inserting assembly, it will generate a script using the game's already existing scripting engine. It is currently only dungeon-only, but when have limits actually limited us? It will insert a custom assembly script to make them work outside of dungeons, which is a very simple hack. So keep your hopes up!


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 22, 2010, 02:49:51 AM
Big update: Decompiling (and also compiling), by interaction ID or address:

(http://img198.imageshack.us/img198/4804/zose.png)


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 23, 2010, 12:01:56 AM
GREAT news! This may lead to being able to edit almost every interaction if my theories and tests are correct! No more being stuck with existing events! It seems that every type 2 (and other) interaction uses the scripting engine, so with a little emulation, ZOSE might be able to decompile/edit the scripts of regular interactions like people and triggers!


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Fatories on October 23, 2010, 12:27:40 AM
GREAT news! This may lead to being able to edit almost every interaction if my theories and tests are correct! No more being stuck with existing events! It seems that every type 2 (and other) interaction uses the scripting engine, so with a little emulation, ZOSE might be able to decompile/edit the scripts of regular interactions like people and triggers!

That is EXACTLY what I want to here Lin!  :) :)
Your theories/tests better work, or you'll crush a boys dreams.


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 23, 2010, 03:39:13 AM
Making great progress:

(http://img295.imageshack.us/img295/7236/zose4.png)


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 23, 2010, 11:40:35 AM
Making more very good progress. I am in the process of writing a small emulator that just emulates code until the scripting procedure is called, where the script address would have been loaded. It works so far. Here's the emulator's code:

Code:
using System;
using System.Collections.Generic;
using System.Text;

namespace ZOSE
{
public class Emulator
{
GBHL.GBFile gb;
Stack<int> stack = new Stack<int>(); //An int because we push the PC onto here which is GBFile.BufferLocation (integer)
byte a, b, c, d, e, f, h, l;
byte bank;
bool zero, carry;
int final = -1;
byte[] ram; //Note: No bank support!

private ushort hl
{
get { return (ushort)(h * 0x100 + l); }
set { h = (byte)(value >> 8); l = (byte)(value & 0xFF); }
}

private ushort af
{
get { return (ushort)(a * 0x100 + f); }
set { a = (byte)(value >> 8); f = (byte)(value & 0xFF); }
}

private ushort bc
{
get { return (ushort)(b * 0x100 + c); }
set { b = (byte)(value >> 8); c = (byte)(value & 0xFF); }
}

private ushort de
{
get { return (ushort)(d * 0x100 + e); }
set { d = (byte)(value >> 8); e = (byte)(value & 0xFF); }
}

public Emulator(GBHL.GBFile g)
{
gb = g;
}

public int EmulateUntilScript(int id, int x, int y)
{
//Some initialization
this.a = b = c = d = e = f = h = l = 0;
//Because this isn't a REAL emulator, we'll set D to... D0
d = 0xD0;
zero = carry = false;
gb.BufferLocation = 0;
ram = new byte[0x10000];
stack = new Stack<int>();
for (int i = 0; i < 0x4000; i++)
ram[i] = gb.ReadByte();
for (int i = 0; i < 0x10; i++)
{
ram[0xD040 + i * 0x100] = 1; //We'll say we have all 16 interactions active
ram[0xD041 + i * 0x100] = (byte)(id >> 8);
ram[0xD042 + i * 0x100] = (byte)id;
ram[0xD04B + i * 0x100] = (byte)y;
ram[0xD04D + i * 0x100] = (byte)x;
//ram[0xD044 + i * 0x100] = 1; //We'll say we have all 16 interactions active
}
for (int i = 0; i < 0x20; i++)
stack.Push(0); //Emulate things for a bit
ram[0xCD00] = 1;
ram[0xC6D2] = 0x2F;
final = -1;
//Calculate the interaction's assembly address
byte first = (byte)(id >> 8);
byte second = (byte)(id);
byte a = first;
byte bank = 0;
if (a < 0x3E) bank = 08;
else if (a < 0x67) bank = 09;
else if (a < 0x98) bank = 0x0A;
else if (a < 0xDC) bank = 0x0B;
else bank = 0x0C;
int pointer = 0x3B8B + (a * 2);
gb.BufferLocation = pointer;
gb.BufferLocation = bank * 0x4000 + gb.ReadByte() + ((gb.ReadByte() - 0x40) * 0x100);

this.bank = bank;
Emulate(gb.BufferLocation);

if (final == -1)
return -1;

return 0x30000 + (final - 0x4000);
}

public int GetAddress()
{
int pointer = gb.ReadByte() + gb.ReadByte() * 0x100;
if (pointer < 0x4000 || pointer > 0x7FFF)
return pointer;
return bank * 0x4000 + pointer - 0x4000;
}

public int Emulate(int address)
{
int retCount = 0;

gb.BufferLocation = address;
byte blank, before;

while (gb.BufferLocation != 0x2518 && retCount != -1)
{
if (final != -1)
return final;
byte opcode = gb.ReadByte();
switch (opcode)
{

case 00: //NOP
break;

case 01: //LD BC,nn
c = gb.ReadByte();
b = gb.ReadByte();
break;

case 05: //DEC B
before = b;
b = (byte)(b - 1);
if (b > before)
carry = true;
else
carry = false;
if (b == 0)
zero = true;
else
zero = false;
break;

case 06: //LD B,n
b = gb.ReadByte();
break;

case 07: //RLCA
a = RotateLeft(a);
if ((a & 1) != 0)
carry = true;
else
carry = false;
if (a == 0)
zero = true;
else
zero = false;
break;

case 09: //ADD HL,BC
hl = (ushort)(hl + bc);
break;

case 0xA: //LD A,(BC)
a = ram[bc];
break;

case 0x0E: //LD C,n
c = gb.ReadByte();
break;

case 0x12: //LD (DE),A
ram[de] = a;
break;

case 0x18: //JR n
sbyte s = (sbyte)gb.ReadByte();
gb.BufferLocation += s;
break;

case 0x1A: //LD A,(DE)
a = ReadByte(de);
break;

case 0x1C: //INC E
before = e;
e = (byte)(e + 1);
if (e < before)
carry = true;
else
carry = false;
if (l == 0)
zero = true;
else
zero = false;
break;

case 0x1E: //LD E,n
e = gb.ReadByte();
break;

case 0x20: //JR NZ,n
blank = gb.ReadByte();
if (!zero)
{
gb.BufferLocation += blank;
}
break;

case 0x21: //LD HL,nn
l = gb.ReadByte();
h = gb.ReadByte();
break;

case 0x22: //LDI (HL),A
ram[hl++] = a;
break;

case 0x24: //INC H
before = h;
h = (byte)(h + 1);
if (h < before)
carry = true;
else
carry = false;
if (h == 0)
zero = true;
else
zero = false;
break;

case 0x28: //JR Z,n
blank = gb.ReadByte();
if (zero)
{
gb.BufferLocation += blank;
}
break;

case 0x2A: //LDI A,(HL)
a = ReadByte(hl++);
break;

case 0x2C: //INC L
before = l;
l = (byte)(l + 1);
if (l < before)
carry = true;
else
carry = false;
if (l == 0)
zero = true;
else
zero = false;
break;

case 0x2E: //LD L,n
l = gb.ReadByte();
break;

case 0x30: //JR NC,n
blank = gb.ReadByte();
if (!carry)
{
gb.BufferLocation += blank;
}
break;

case 0x35: //DEC (HL)
before = ram[hl];
ram[hl] = (byte)(ram[hl] - 1);
if (ram[hl] > before)
carry = true;
else
carry = false;
if (ram[hl] == 0)
zero = true;
else
zero = false;
break;

case 0x38: //JR C,n
blank = gb.ReadByte();
if (carry)
{
gb.BufferLocation += blank;
}
break;

case 0x3C: //INC A
before = a;
a = (byte)(a + 1);
if (a < before)
carry = true;
else
carry = false;
if (l == 0)
zero = true;
else
zero = false;
break;

case 0x3E: //LD A,n
a = gb.ReadByte();
break;

case 0x44: //LD B,H
b = h;
break;

case 0x47: //LD B,A
b = a;
break;

case 0x4F: //LD C,A
c = a;
break;

case 0x62: //LD H,D
h = d;
break;

case 0x66: //LD H,(HL)
h = ReadByte(hl);
break;

case 0x6F: //LD L,A
l = a;
break;

case 0x70: //LD (HL),B
ram[hl] = b;
break;

case 0x71: //LD (HL),C
ram[hl] = c;
break;

case 0x77: //LD (HL),A
ram[hl] = a;
break;

case 0x78: //LD A,B
a = b;
break;

case 0x79: //LD A,C
a = c;
break;

case 0x7C: //LD A,H
a = h;
break;

case 0x7D: //LD A,L
a = l;
break;

case 0x7E: //LD A,(HL)
a = ReadByte(hl);
break;

case 0x81: //ADD C
before = a;
a = (byte)(a + c);
if (a < before || (a == before && c != 0))
carry = true;
else
carry = false;
if (a == 0)
zero = true;
else
zero = false;
break;

case 0x85: //ADD L
before = a;
a = (byte)(a + l);
if (a < before || (a == before && l != 0))
carry = true;
else
carry = false;
if (a == 0)
zero = true;
else
zero = false;
break;

case 0x87: //ADD A
before = a;
a = (byte)(a + a);
if (a < before || (a == before && before != 0))
carry = true;
else
carry = false;
if (a == 0)
zero = true;
else
zero = false;
break;

case 0x91: //SUB C
before = a;
a = (byte)(a - c);
if (a > before || (a == before && c != 0))
carry = true;
else
carry = false;
if (a == 0)
zero = true;
else
zero = false;
break;

case 0xA6: //AND (HL)
a = (byte)(a & ram[hl]);
if (a == 0)
zero = true;
else
zero = false;
break;

case 0xAF: //XOR A
a = (byte)(a ^ a);
if (a == 0)
zero = true;
else
zero = false;
break;

case 0xB7: //OR A
a = (byte)(a | a);
if (a == 0)
zero = true;
else
zero = true;
break;

case 0xB8: //CP B
if (a == b)
zero = true;
else
zero = false;
break;

case 0xC0: //RET NZ
if (!zero)
{
retCount--;
gb.BufferLocation = PopPC();
break;
}
break;

case 0xC1: //POP BC
bc = (ushort)stack.Pop();
break;

case 0xC2: //JP NZ,nn
int addr = gb.ReadByte() + gb.ReadByte() * 0x100;
if (!zero)
{
gb.BufferLocation = CalculatePC(addr);
}
break;

case 0xC3: //JP nn
gb.BufferLocation = CalculatePC(gb.ReadByte() + gb.ReadByte() * 0x100);
break;

case 0xC5: //PUSH BC
stack.Push(bc);
break;

case 0xC7: //RST 00
PushPC();
Emulate(0);
break;

case 0xC8: //RET Z
if (zero)
{
retCount--;
gb.BufferLocation = PopPC();
break;
}
break;

case 0xC9: //RET
retCount--;
gb.BufferLocation = PopPC();
break;

case 0xCB: //VARYING
switch (gb.ReadByte())
{
case 0x37: //SWAP A
a = (byte)(((a & 0xF) << 4) + (a >> 4));
break;
}
break;

case 0xCD: //Call
address = GetAddress();
PushPC();
if (address == 0x2518)
{
final = hl;
return final;
}
if (address == 0x2544)
{
final = hl;
return final;
}
int i = Emulate(address);
/*if (address == 0x2518)
{
final = hl;
return final;
}
gb.BufferLocation = PopPC();*/
break;

case 0xD1: //POP DE
de = (ushort)stack.Pop();
break;

case 0xD9: //RETI
retCount--;
gb.BufferLocation = PopPC();
break;

case 0xDF: //RST 18
PushPC();
Emulate(0x18);
break;

case 0xE1: //POP HL
hl = (ushort)stack.Pop();
break;

case 0xE5: //PUSH HL
stack.Push(hl);
break;

case 0xE6: //AND A,n
a &= gb.ReadByte();
break;

case 0xE9: //JP HL
gb.BufferLocation = CalculatePC(hl);
break;

case 0xEF: //RST 28
PushPC();
Emulate(0x28);
break;

case 0xF0: //LD A,(FF00+n)
a = ram[0xFF00 + gb.ReadByte()];
break;

case 0xF1: //POP AF
af = (ushort)stack.Pop();
break;

case 0xF6: //OR A,n
a = (byte)(a | gb.ReadByte());
break;

case 0xFA: //LD A,(nn)
a = ReadByte(GetAddress());
break;

case 0xFE: //CP A,n
if (a == gb.ReadByte())
zero = true;
else
zero = false;
break;

default:
opcode = (byte)(opcode - 0);
break;
}
}

if (retCount == -1)
return -1;
return gb.BufferLocation;
}

public byte RotateLeft(byte b)
{
byte carry = (byte)(b >> 7);
return (byte)((b << 1) | carry);
}

public byte ReadByte(int address)
{
if (address < 0x4000 || address > 0x7FFF)
return ram[address];
return gb.Buffer[bank * 0x4000 + address - 0x4000];
}

public int CalculatePC(int address)
{
if (address < 0x4000)
return address;
return bank * 0x4000 + address - 0x4000;
}

public void PushPC()
{
int address = gb.BufferLocation;
if (address < 0x4000)
{
stack.Push(address);
return;
}
address = address % 0x4000;
address += 0x4000;
stack.Push(address);
}

public int PopPC()
{
int address = stack.Pop();
if (address < 0x4000)
return address;
address += bank * 0x4000 - 0x4000;
return address;
}
}
}

Beautiful, isn't it? Because of that, we'll be able view the scripts of countless more interactions and increase the flexibility of Oracles editing by loads.


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 24, 2010, 12:00:06 AM
Well, I've decided to give up on the emulator. There are too many things to worry about and it's too much work to test and work on. So, instead I'll be adding custom interactions and a patch in ZOLE that will allow the scripting of either any interaction or my own interaction ID. Sorry.


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 24, 2010, 10:59:52 PM
Good news. I have overwrote opcode FC, which was the final delay-setting opcode, and made it a 3-byte jump opcode. What happens is it reads from where you tell it to read and writes the bytes in the memory (At C300 since it seems vacant). Then it emulates the data there as normal, so now instead of being with limited space, we have the whole ROM to do as we please. However, we still need to use bank 0C to initialize things, so as soon as we set the write location, we do jump and set the write location to something else!


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 25, 2010, 06:18:13 AM
Great news. Following up on the opcode FC change, I rewrote some stuff in ZOSE to allow creating more than one script at once, and later decompile one and decompile whatever it jumps to. In other news, I have created interaction 72 (I destroyed the old one and created my own) which is basically like interaction 20, except the scripts can be used from anywhere, including overworlds and dungeons. This means we can now use ZOSE to create any scripting event we want anywhere, which means hacks will become much better! Here's all it took:

Code:
gb.WriteBytes(0x2BE09, new byte[] { 0x1E, 0x42, 0x21, 0x19, 0x7E, 0x1A, 0xDF, 0x2A, 0x66, 0x6F, 0xCD, 0x44, 0x25, 0xC3, 0x52, 0x25 });
gb.WriteByte(0x3C6F, 0x09);
gb.WriteByte(0x3C70, 0x7E);

This new script allows 243 scripts to be used (Sorry it's not 256. There wasn't enough space in bank 0xA). The only problem we face is the space we're left with in bank 0C, but each script we create will only need 6 bytes there. Pretty neat, huh?


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: m64m on October 26, 2010, 04:06:51 PM
This sounds totally awesome. Great work =D


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 28, 2010, 05:30:53 AM
Okay. Although I haven't worked on ZOSE too much due to being sick, I did manage to fix all the interaction 72 and jump3byte (opcode FC) bugs. This is all it takes to make a chest appear with a puff and sound effect when you've killed all enemies:

(http://img109.imageshack.us/img109/6010/zose6.png)


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Supreme Dirt on October 30, 2010, 11:45:01 PM
How feasible will it be to make enemies do things, like say an enemy moving a pot in a boss battle? Will it be possible to change enemy and boss AI with this?


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 30, 2010, 11:46:28 PM
No we won't. I'm pretty sure that's all assembly, but you never know. If there's anyone who would know the answer to that question, it's Jigglysaint. Even if we did know how the AI works, just remember, it's even hard in regular games so it'll surely be hard in these.


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Supreme Dirt on October 30, 2010, 11:53:12 PM
Well, that won't stop me from making the boss battle I have in mind. I'll just have to do it differently...


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Gamma 31 on October 30, 2010, 11:56:31 PM
Hmm... I'm guessing you still need to know stuff about (OoA) scripting to use ZOSE? How did you learn about it, Lin?


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on October 31, 2010, 12:19:05 AM
Mainly debugging stuff, and Jigglysaint gave me a good start on it, so in a way you could say Jigglysaint taught me some stuff about it.


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Gamma 31 on October 31, 2010, 12:27:46 AM
OK...


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on November 01, 2010, 04:24:59 AM
Apart from my heavy slacking on this program, I have good news. Today I discussed things with Jigglysaint, and eventually the idea was formed to move the opcode pointer table and me making opcode FF and multi-opcode. Let's look at the benefits:

-More space in bank 0C which means more custom scripts
-Existing scripts will not break
-I can add 768 custom opcodes (FD xx, FE xx, FF xx)
-More flexible scripts (because of the above)

Sounds good, right? The bad side is it's not going to be fun programming the code because there is no good assembler. Oh well, it is what it is!


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on November 03, 2010, 12:32:11 AM
Okay, right now ZOSE repoints the opcode pointer to bank $40 and everything is going to plan. However, it seems for events where a trigger spawns a chest, you can keep spawning it after you leave the room. I have to find out why.

EDIT: It seems that for the item check opcode, the assembly is where the pointer for opcode FD would be, and I deleted that. So basically, I deleted the code for it... Simple fix :)


Title: Re: ZOSE - Zelda Oracles Script Editor [WIP]
Post by: Lin on November 06, 2010, 11:51:00 PM
Okay, I know I haven't been working on it too much since the last post, but I started again last night and got frustrated and went to bed. Then I woke up this morning, deleted and revised a lot of what I did last night, and now there's a perfect auto-completion feature:

http://img200.imageshack.us/img200/5972/zose7.png

The list appears and populates as you type, and when you press space, the command finishes and adds a space, and when you press enter, the command finishes and adds a new line. Pretty neat, huh? It took a while because it seems whenever I do character-by-character text manipulation, it's always very buggy the first time(s) around. Anyway, that's that. Except a release... kinda soon.

~Lin


Title: Re: ZOSE - Zelda Oracles Script Editor
Post by: mathewlan13 on March 22, 2012, 01:01:07 PM
How do you use this script?
Do you put it in ZOSE?


Title: Re: ZOSE - Zelda Oracles Script Editor
Post by: Fatories on March 22, 2012, 09:03:24 PM
How do you use this script?
Do you put it in ZOSE?

What script? If you mean any code that's shown in this specific thread then you don't use any of it because its all outdated and/or incorrect. Go to the ZOSE forum for example scripts and tutorials on how to use the program.