Monday, March 11, 2013

gba4ds compiling

If you're having problems compiling GBA4DS, you'll need to edit the make file in bootstub to reflect the change to arm-none-eabi*

You will also need a copy of the R4 menu in the root dir of your source.

Sunday, March 3, 2013

Work

Hard at work working on something for y'all please stayed tuned!

Saturday, February 9, 2013

Decompilation example

Quick example of how well the plugin works



void RestoreHealth(){ //Original function
u16* curHealth=(u16*)0x3001310;
u16* maxHealth=(u16*)0x3001312;
unsigned short val=*maxHealth-*curHealth;
*curHealth+=val;

}


Here's the assembeled copy
            EXPORT RestoreHealth
RestoreHealth

var_8           = -8
var_4           = -4

                SUB     SP, SP, #0x10
                LDR     R3, =0x3001310
                STR     R3, [SP,#0x10+var_4]
                LDR     R3, =0x3001312
                STR     R3, [SP,#0x10+var_8]
                LDR     R3, [SP,#0x10+var_8]
                LDRH    R1, [R3]
                LDR     R3, [SP,#0x10+var_4]
                LDRH    R2, [R3]
                MOV     R3, SP
                ADDS    R3, #6
                SUBS    R2, R1, R2
                STRH    R2, [R3]
                LDR     R3, [SP,#0x10+var_4]
                LDRH    R2, [R3]
                MOV     R3, SP
                ADDS    R3, #6
                LDRH    R3, [R3]
                ADDS    R3, R2, R3
                LSLS    R3, R3, #0x10
                LSRS    R2, R3, #0x10
                LDR     R3, [SP,#0x10+var_4]
                STRH    R2, [R3]
                ADD     SP, SP, #0x10
                BX      LR
; End of function RestoreHealth

; ---------------------------------------------------------------------------
                ALIGN 0x10
dword_8000320   DCD 0x3001310           ; DATA XREF: RestoreHealth+2 r
dword_8000324   DCD 0x3001312           ; DATA XREF: RestoreHealth+6 r

And here's the decompiled version! 


; ---------------------------------------------------------------------------

                EXPORT RestoreHealth
RestoreHealth
                void RestoreHealth() { // framesize 0x10

                 (unsigned long)SP+0xC = 0x3001310;
                 (unsigned long)SP+8 = 0x3001312;
                 (unsigned short)SP+6 = word<(unsigned long)SP+8> - word<(unsigned long)SP+0xC>;
                 word<(unsigned long)SP+0xC> = word<(unsigned long)SP+0xC> + (unsigned short)SP+6 & 0xFFFF;
                 return void
                }
; ---------------------------------------------------------------------------

Decompiler update

Greetings!


There is now a repository for the decompiler!
http://code.google.com/p/arm-thumb-decompiler-plugin/
If you wish to be a committer please e-mail myself or normmatt.

Helpers are welcome! :)


Here's the current r11 binaries.

https://dl.dropbox.com/u/12510094/NEVERDELETE/decompilerr11.zip


Romhacking update soon, maybe today.

Best jump table detection!


Monday, February 4, 2013

Fixed a bug in the decompiler

Hello!

Last release of the decompiler could only detect switches in the following format.

80 00   LSL   R0, R0, #2

02 49   LDR   R1, =off_83C173C
40 18   ADD   R0, R0, R1
00 68   LDR   R0, [R0]
87 46   MOV   PC, R0

I successfully have it reading switches from r1, and will be advancing it to switches with any registers.

I also have to look into getting default case switches to work.

Expect a release soon!

Sunday, February 3, 2013

Thumb Decompiler plugin

Hello!

Here's the first release of that decompiler I've been talking about. It's a huge help, thank you Ludde.


Updates will come and I'll eventually get a googlecode for it.


Little tutorial on setup


Hello! This is a quick tutorial on how to use the updated version of Luddes, thumdecompiler.

Easy to use, with powerful results. That man is a genius. Anyway, it's helped me a lot and I hope it can help with malicious, or bad code or debug something that got compiled wrong

https://dl.dropbox.com/u/12510094/NEVERDELETE/IDATHUMBDECOMPILER.zip Plug in link

https://dl.dropbox.com/u/12510094/NEVERDELETE/thumb.zip Source code.
So....There are a ton of event check functions.

A lot are repeated, I believe the ones I labelled 2 are to check for the event to have occurred  and ones without two are to check if the event is the current event or not

Anyway,


;



EVENT_AfterAuxEventSET:                 ; 0x801C0E8+2
                int EVENT_AfterAuxEventSET() {

                 R1 = (EventCounter != 0x4B ? 0 : 1)
                 return R1
                }
; End of function EVENT_AfterAuxEventSET


;



EVENT_EngagedSaXSET:                    ; 0x801C0E8:loc_801C120
                int EVENT_EngagedSaXSET() {

                 R1 = (EventCounter != 0x4C ? 0 : 1)
                 return R1
                }
; End of function EVENT_EngagedSaXSET


;



EVENT_IceBeamSET:                       ; 0x8010220+730
                                        ; sub_8010220+7C2 ...
                int EVENT_IceBeamSET() {

                 R1 = (EventCounter <=u 0x6A ? 0 : 1)
                 return R1
                }
; End of function EVENT_IceBeamSET


;



EVENT_FinalTripToHangerSET:             ; 0x8054978+A
                                        ; sub_8054A3C+2A ...
                int EVENT_FinalTripToHangerSET() {

                 R1 = (EventCounter <=u 0x69 ? 0 : 1)
                 return R1
                }
; End of function EVENT_FinalTripToHangerSET


;



EVENT_FinalSAXFightSET:                 ; 0x801A914+2
                int EVENT_FinalSAXFightSET() {

                 R1 = (EventCounter != 0x65 ? 0 : 1)
                 return R1
                }
; End of function EVENT_FinalSAXFightSET


;



EVENT_BoxQuakeSET:                      ; CODE XREF: Enemy9F_AI+1B0
                int EVENT_BoxQuakeSET() {

                 R1 = (EventCounter != 0x5A ? 0 : 1)
                 return R1
                }
; End of function EVENT_BoxQuakeSET


;



EVENT_RestricttedLabBlockedSET:         ; CODE XREF: Enemy9F_AI:loc_8030358
                int EVENT_RestricttedLabBlockedSET() {

                 R1 = (EventCounter != 0x59 ? 0 : 1)
                 return R1
                }
; End of function EVENT_RestricttedLabBlockedSET


;



EVENT_HasVariaSET:                      ; 0x80412F0+2C
                int EVENT_HasVariaSET() {

                 R1 = (EventCounter <=u 0x33 ? 0 : 1)
                 return R1
                }
; End of function EVENT_HasVariaSET


;



EVENT_IsPowerOutSET:                    ; 0x8056DF4+4A
                int EVENT_IsPowerOutSET() {

                 R1 = EventCounter
                 R0 = 1
                 if (R1 == 0x46)
                    return R0

                 R0 = 0
                 if (R1 <=u 0x46)
                    return R0

                 return 2
                }
; End of function EVENT_IsPowerOutSET


;



EVENT_HasWaveBeamSET:                   ; CODE XREF: Enemy9F_AI:loc_8030340
                int EVENT_HasWaveBeamSET() {

                 R1 = (EventCounter != 0x5B ? 0 : 1)
                 return R1
                }
; End of function EVENT_HasWaveBeamSET


;



EVENT_ReachedDeadEndRestrictedSET:      ; CODE XREF: Enemy9F_AI:loc_803034C
                                        ; Enemy9F_AI+1DC ...
                int EVENT_ReachedDeadEndRestrictedSET() {

                 R1 = (EventCounter != 0x5C ? 0 : 1)
                 return R1
                }
; End of function EVENT_ReachedDeadEndRestrictedSET


;



EVENT_AfterBombsSET:                    ; CODE XREF: Enemy9F_AI+184
                int EVENT_AfterBombsSET() {

                 R1 = (EventCounter != 0x17 ? 0 : 1)
                 return R1
                }
; End of function EVENT_AfterBombsSET


;



sub_8060C78:                            ; CODE XREF: Enemy9F_AI:loc_8030334
                int sub_8060C78() {

                 R1 = (EventCounter != 0x16 ? 0 : 1)
                 return R1
                }
; End of function sub_8060C78


;



EVENT_EscSaxBeforVariaSET:              ; 0x804378C+E0
                int EVENT_EscSaxBeforVariaSET() {

                 R1 = (EventCounter != 0x31 ? 0 : 1)
                 return R1
                }
; End of function EVENT_EscSaxBeforVariaSET


;



EVENT_SAXEntersPostPBSET:               ; 0x80193C4:loc_8019428
                                        ; sub_8019520+4
                int EVENT_SAXEntersPostPBSET() {

                 R1 = (EventCounter != 0x43 ? 0 : 1)
                 return R1
                }
; End of function EVENT_SAXEntersPostPBSET


;



sub_8060CC0:                            ; 0x80193C4+3C
                int sub_8060CC0() {

                 R1 = (EventCounter != 0x42 ? 0 : 1)
                 return R1
                }
; End of function sub_8060CC0


;



EVENT_SAXEntersPreVariaSET:             ; CODE XREF: PowerBombSaxInit+F
                                        ; BeginSAXRoutine+2
                int EVENT_SAXEntersPreVariaSET() {

                 R1 = (EventCounter != 0x30 ? 0 : 1)
                 return R1
                }
; End of function EVENT_SAXEntersPreVariaSET


;



EVENT_GoGetVariaSET:                    ; CODE XREF: PowerBombSaxInit+8
                int EVENT_GoGetVariaSET() {

                 R1 = (EventCounter != 0x2F ? 0 : 1)
                 return R1
                }
; End of function EVENT_GoGetVariaSET


;



EVENT_GotHighJumpSET:                   ; CODE XREF: SomeSaxRoutine+2
                int EVENT_GotHighJumpSET() {

                 R1 = (EventCounter != 0x19 ? 0 : 1)
                 return R1
                }
; End of function EVENT_GotHighJumpSET


;



EVENT_GotHighJump2SET:                  ; 0x80429B8+2
                                        ; Enemy89_AI:loc_8042B7A
                int EVENT_GotHighJump2SET() {

                 R1 = (EventCounter <=u 0x19 ? 0 : 1)
                 return R1
                }
; End of function EVENT_GotHighJump2SET


;



EVENT_TriggeredBOXSET:                  ; 0x8037E18+6E
                                        ; sub_803808C+242 ...
                int EVENT_TriggeredBOXSET() {

                 R1 = (EventCounter <=u 0x27 ? 0 : 1)
                 return R1
                }
; End of function EVENT_TriggeredBOXSET


;



EVENT_TriggeredBOX2SET:                 ; CODE XREF: Enemy20_AI:loc_803061A
                                        ; sub_8035C58+4
                int EVENT_TriggeredBOX2SET() {

                 R1 = (EventCounter != 0x27 ? 0 : 1)
                 return R1
                }
; End of function EVENT_TriggeredBOX2SET


;



EVENT_HaveSuperMissilesSET:             ; CODE XREF: Enemy9F_AI+14C
                                        ; Enemy20_AI:loc_803059E
                int EVENT_HaveSuperMissilesSET() {

                 R1 = (EventCounter != 0x26 ? 0 : 1)
                 return R1
                }
; End of function EVENT_HaveSuperMissilesSET


;



EVENT_IsInSuperMissileRoomSET:          ; CODE XREF: Enemy9F_AI:loc_8030328
                int EVENT_IsInSuperMissileRoomSET() {

                 R1 = (EventCounter != 0x25 ? 0 : 1)
                 return R1
                }
; End of function EVENT_IsInSuperMissileRoomSET


;



EVENT_EnteredPumpControlSET:            ; 0x8030E14:loc_8030E96
                int EVENT_EnteredPumpControlSET() {

                 R1 = (EventCounter <=u 0x1F ? 0 : 1)
                 return R1
                }
; End of function EVENT_EnteredPumpControlSET


;



EVENT_OnToQuarantineBaySET:             ; 0x801CA1C+A
                int EVENT_OnToQuarantineBaySET() {

                 R1 = (EventCounter != 1 ? 0 : 1)
                 return R1
                }
; End of function EVENT_OnToQuarantineBaySET


;



EVENT_GenericSet:                       ; 0x801F2C0+36
                int EVENT_GenericSet() {

                 R2 = 0
                 R1 = EventCounter
                 if ((R1 - 0x3B & 0xFF) <=u 1 || (R1 - 0x46 & 0xFF) <=u 3 || R1 >u 0x66)
                    R2 = 1

                 return R2
                }
; End of function EVENT_GenericSet


;



EVENT_Generic2Set:                      ; 0x802A11C+5C
                int EVENT_Generic2Set() {

                 R2 = 0
                 R1 = EventCounter
                 if ((R1 - 0x46 & 0xFF) <=u 3 || R1 >u 0x66)
                    R2 = 1

                 return R2
                }
; End of function EVENT_Generic2Set


;



EVENT_Generic3Set:                      ; 0x802BEC4+42
                int EVENT_Generic3Set() {

                 R1 = ((EventCounter - 0x46 & 0xFF) >u 3 ? 0 : 1)
                 return R1
                }
; End of function EVENT_Generic3Set

                .align 4
                .long EventCounter

;



EVENT_EnteredPumpControl2SET:           ; 0x8039248:loc_80392AE
                int EVENT_EnteredPumpControl2SET() {

                 R1 = (EventCounter != 0x1F ? 0 : 1)
                 return R1
                }
; End of function EVENT_EnteredPumpControl2SET


;



EVENT_WideBeamGETorMeltdownSET:         ; 0x8039248:loc_80392B4
                                        ; sub_8039680+A ...
                int EVENT_WideBeamGETorMeltdownSET() {

                 R1 = 0
                 R0 = EventCounter
                 if (R0 == 0x3B) {
                    R1 = 1
                    return R1
                 }

                 if (R0 == 0x3C)
                    R1 = 2

                 return R1
                }
; End of function EVENT_WideBeamGETorMeltdownSET


;



EVENT_TharBeWildLifeSET:                ; 0x8039248:loc_80392E6
                int EVENT_TharBeWildLifeSET() {

                 R1 = (EventCounter != 0x3E ? 0 : 1)
                 return R1
                }
; End of function EVENT_TharBeWildLifeSET


;



EVENT_TharBeWildLife2SET:               ; 0x8030868+BE
                                        ; sub_80309A0:loc_80309B2 ...
                int EVENT_TharBeWildLife2SET() {

                 R1 = (EventCounter <=u 0x3E ? 0 : 1)
                 return R1
                }
; End of function EVENT_TharBeWildLife2SET


;



EVENT_HasSpaceJump:                     ; 0x8039248:loc_80392DC
                int EVENT_HasSpaceJump() {

                 R1 = (EventCounter != 0x49 ? 0 : 1)
                 return R1
                }
; End of function EVENT_HasSpaceJump


;



EVENT_SAXSaviorEscapesSET:              ; 0x80399B0+64
                int EVENT_SAXSaviorEscapesSET() {

                 R1 = (EventCounter != 0x66 ? 0 : 1)
                 return R1
                }
; End of function EVENT_SAXSaviorEscapesSET


;



EVENT_SAXSaviorEscapes2SET:             ; 0x80399B0:loc_8039A40
                int EVENT_SAXSaviorEscapes2SET() {

                 R1 = (EventCounter <=u 0x66 ? 0 : 1)
                 return R1
                }
;EVENT_SAXSaviorEscapes2SET


;


GetWhichTimer:                          ; 0x802A310+2
                                        ; sub_804F614+2 ...
                int GetWhichTimer() {

                 R2 = 0
                 R0 = EventCounter - 0x3B
                 if (R0 >u 0x31)
                    return R2

                 if (R0 == 0 || R0 == 1) {
                    R2 = 1
                    return R2
                 }

                 if (R0 == 2 || R0 == 3 || R0 == 4 || R0 == 5 || R0 == 6 || R0 == 7 || R0 == 8 || R0 == 9 || R0 == 0xA || R0 == 0xB || R0 == 0xC || R0 == 0xD || R0 == 0xE || R0 == 0xF || R0 == 0x10 || R0 == 0x11 || R0 == 0x12 || R0 == 0x13 || R0 == 0x14 || R0 == 0x15 || R0 == 0x16 || R0 == 0x17 || R0 == 0x18 || R0 == 0x19 || R0 == 0x1A || R0 == 0x1B || R0 == 0x1C || R0 == 0x1D || R0 == 0x1E || R0
                    return R2

                 if (R0 == 0x22) {
                    R2 = 2
                    return R2
                 }

                 if (R0 == 0x23 || R0 == 0x24 || R0 == 0x25 || R0 == 0x26 || R0 == 0x27 || R0 == 0x28 || R0 == 0x29 || R0 == 0x2A || R0 == 0x2B)
                    return R2

                 if (R0 != 0x2C && R0 != 0x2D && R0 != 0x2E && R0 != 0x2F && R0 == 0x30) {
                 }

                 R2 = 3
                 return R2
                }
;GetWhichTimer


;



EVENT_CheckMajorEventsSET:              ; 0x80746CC+64 p
                int EVENT_CheckMajorEventsSET() {

                 R1 = 0
                 R0 = EventCounter
                 if (R0 == 0x3D || R0 == 0x5E || R0 == 0x6D)
                    R1 = 1

                 return R1
                }
;EVENT_CheckMajorEventsSET

ASM post coming soon!

Sunday or Monday, the next tutorial will be up!

We will again be hacking Metroid Fusion.
Our goal this time, will be to completely fill up our energy whenever we pass through a door.

I will be issuing homework I hope some will try it!

We will be learning how to track down our target addresses.
Learn how to Hi-Jack addresses
And how to return :D

I'm not the best, but it works well and that's how I'm teaching!

Tuesday, January 29, 2013

Romhacking misconceptions

Today I'd like to talk about a few common misconceptions when hacking.

"I can't hex"

Look bro, I don't care who you are "hex" is not an action. It's a type of counting system
01-0F, and so on.

Just forget the term hex if it bothers you that much. Just refer to it as data however.

If you see 0x

Such as 0x0BADC0DE the 0x indicates we're using the hexadecimal system.

Now when you pull up a ROM in a hex editor, you'll see this



This image shows many things, but you're probably confused.


This is called a GBA header. It's of course stored in a binary data format. But since we know it's a GBA game header, we can just find the header structure on google and apply the info.

Here's the structure


0x000h 4 ROM Entry Point (32bit ARM branch opcode, eg. "B rom_start")  0x004h 156 Nintendo Logo (compressed bitmap, required!)
 0x0A0h 12 Game Title (uppercase ascii, max 12 characters)
 0x0ACh 4 Game Code (uppercase ascii, 4 characters)
 0x0B0h 2 Maker Code (uppercase ascii, 2 characters)
 0x0B2h 1 Fixed value (must be 96h, required!)
 0x0B3h 1 Main unit code (00h for current GBA models)
0x0B4h 1 Device type (usually 00h)
 0x0B5h 7 Reserved Area (should be zero filled)
 0x0BCh 1 Software version (usually 00h)
 0x0BDh 1 Complement check (header checksum, required!)
 0x0BEh 2 Reserved Area (should be zero filled)

So according to this, the first 4 bytes are an assembly instruction on where to start the game.
Then is a compressed image(Yes, that's stored in our hexadecimal data format)

Then so on.

Data is nothing to be scared of. The editors you use, the game itself relies on 
the data.

Let's move on to pointers.




They're scary for a lot of newbies. I didn't quite get them when I first started,but now I rely on them.

It's super simple
For GBA

it's just address+0x8000000 and it's stored backwards in the rom

So the pointer 0x08123456 would be stored in rom at 56 34 12 08

Codes uses the pointer to get the address of the data.
Just add 0x8000000 for ROM pointers. Simple no?

Assembly is just a way of seeing the data as somewhat readable

nop instead of 0x46c0.

It's not hard and just needs to be learned.

If you have any questions, leave a comment. I'll get back to you.






Sunday, January 27, 2013

Thumb Decompiler update

Hey guys!

So the progress of the Thumb Decompiler plugin is going great. I'll be releasing the update for it soon. Source included of course as per the original thumb decompiler.

Here's a screenie!

I need to get the decompiled code to redirect to a new tab, get stack variables going. And some other stuff. But hopefully soon!

Friday, January 25, 2013

Your first GBA ASM hack!

Greetings! Today I will teach you how to find what you need to find using a debugger, then how to change it. Then what else you can do!

You will need no$gba debugger, or a sufficient GBA emulator with a good debugger. Grab a Metroid Fusion ROM(Rip your own preferably) and get a hex editor. I heard HxD is good.

We are going to find out how to get rid of Samus freezing her butt off in NOC with no Varia suit.
So, grab your emulator, load Fusion and head to ARC.

We'll need to know the current health samus has, load up Datacrystal.org and go to Fusion then RAM Map. This gives us the address of 3001310. Head to the room before it gets icy.

In no$gba(Or whatever debugger you have) set a breakpoint for when Samus's health gets a write(That means when something changes it) hit ctrl+b on your keyboard!
Now enter [3001310]!!(Or the equivalent in your debugger)
Continue playing until the emulator stops.

Woo! It hit.

You'll see something like

What do these lines mean?


080063CE LDRH    R0, [R2]
080063D0 SUBS    R0, #1
080063D2 STRH    R0, [R2]
080063D4 LDRH    R0, [R2] <---You'll break here

You'll basically encounter 3 variable types when hacking


Byte, Short, Long
1 Byte for a byte, 2 bytes for a short and 4 bytes for a long
Byte can hold 0-255
Short can hold 0-65535(0xFFFF)
And Long can hold 0-4294967295(0xFFFFFFFF)
Wow that's a lot!



So what does the following mean?!

080063CE LDRH    R0, [R2]
080063D0 SUBS    R0, #1
080063D2 STRH    R0, [R2]
080063D4 LDRH    R0, [R2]




LDRH means LoadRegisterHalfword

or Load a short

What it does is it reads 2 bytes from whatever address is in the Register R2 and puts them into 0


STRH is the same, except it stores.


And of course SUBS R0, #1 is subtract 1 from R0

In this tutorial we'll do 3 different things.

Delete the breakpoint, and write down the offset 080063D0 which has the SUBs instruction.


Make a save state and let's do some hacking


In your debugger, go to
080063D0

type nop in, and it will appear in the debugger.
Now go to the frozen zone and bamf! No more damage.

Reload the rom and open your savestate, go to that address again. Type in ADD R0, 3


Then watch Samus gain health quickly.




Now, let's change it back to nop look at the two bytes to the right of the subs r0, #1 instruction.
3801

Now if you go to the data viewer(At least in no$gba) and go to that address it'll look like

01 38

AND THAT IS A'OKAY.

Now change the instruction to nop then those two bytes next to nop will be 46C0, and again if you look in the dataviewer it will be c0 46!


Now, that's cool but these results aren't saved to our ROM

Open up the ROM in your hex editor!

Let's have a quick lesson about GBA Addressing.

It's super simple.

The address 0x080063D0 we're playing with, in your hexeditor will be at 0x63D0
Just subtract 0x8000000 to convert(Or add in case of pointers)
So now go to  0x63D0 type c0 46 at the offset, save then test! No more damage in ARC! :)

Video included.






IDA Pro is best

Several weeks ago I found a plugin called Thumb decompiler, it does as it says. However it's around 6 or 7 years old and hasn't been updated. I grabbed the source and began modifying it, so far I only managed to fix one bug. But the plugins code is genius, it's made by a person named Ludde. Genius, simply genius. I'm adding to the plugin currently working on getting the decompiled code to it's own subview. I just figured out how to create subviews so it should be a piece of cake from there here's a picture!
I will release the plugin on this blog once I have substantial work put into it such as getting stack vars going and such.

Messing around with SA-X

Just a couple videos of me messing with SA-X

Just a funny few

Thursday, January 17, 2013

Metroid Fusions debug menu and Zero Missions forced lack of

Greetings!

I have a vast love for the Metroid series. It was the one I played with my parents when I was a toddler and such. I sadly only own Zero Mission and Fusion.


A friend of mine had told of me debug event text in Metroid Fusion, I have started a hack of Fusion to expand on it and I wanted this info for my ROM map. So he gives it to me, then I find what looks like a DrawText function near it and knew I was on to something. Faster forward 15 hours later, we had a full working Metroid Fusion debug menu. That NO ONE knew about outside of Nintendo for 11 years!

It's extremely powerful. I love Nintendo so hard.

So here is a video of it!


And so I also delved in the ZM rom for a bit.

While I didn't find anything new, I did look at Trunaur86s old menu hack for the on/off button.

When looking at the code, I discovered they intentionally removed access to it.

There's a register 'R6' which hold basically the onffswitch val. And the sets it to 0 and does checks to make sure it's 0, where as if it's non-zero you get the onoffswitch!


Sunday, January 6, 2013

New projects

Greetings,

I'm currently undertaking many projects and so I'll be posting here with how I'm doing them. Or tutorials to get others started.

So, that's it for this one. I'll probably begin another one today