Initial commit

This commit is contained in:
Aiden Isik 2025-01-13 19:39:44 +00:00
commit 5a9c66ea87
2 changed files with 238 additions and 0 deletions

7
build.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
fcx-as -o cause360error.o -mregnames -a32 -mppc64 cause360error.s
fcx-ld --accept-unknown-input-arch --oformat=pei-i386 --subsystem xbox --major-subsystem-version 1 --minor-subsystem-version 0 --major-os-version 0 --minor-os-version 0 --section-alignment=0x10000 --file-alignment 512 --disable-dynamicbase --image-base 0x82000000 --disable-long-section-names -e _start -o cause360error.exe cause360error.o
synthxex --skip-machine-check --input cause360error.exe --output cause360error.xex

231
cause360error.s Normal file
View file

@ -0,0 +1,231 @@
# Assembly program to make the Xbox 360 display an error code
#
# Copyright (c) 2025 Aiden Isik
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
.global _start
# Code
.text
# Read 32-bit little endian value and byte-swap it
# IN: r4 == base address of little endian value
# IN: LR == address to return to
# OUT: r4 == read and byteswapped value
get32BitLittleEndian:
# r5 == how many bytes to left shift each value read
# r6 == working register used to store currently retrieved bytes
# r7 == register to store current byte before shift
li r5, 24 # Register storing how many bits to shift each value
# Most significant byte
lbz r6, 3(r4)
slw r6, r6, r5
subi r5, r5, 8
lbz r7, 2(r4)
slw r7, r7, r5
subi r5, r5, 8
or r6, r6, r7
lbz r7, 1(r4)
slw r7, r7, r5
or r6, r6, r7
# Least significant byte
lbz r7, 0(r4)
or r6, r6, r7
mr r4, r6 # Final value
blr
# If the function was not found, return 0 (set r3 = 0, then return)
# IN: r12 == address to return to
# OUT: r3 == 0
kernelFunctionNotFound:
li r3, 0
mtlr r12
blr
# So I don't know if this works or not: it turns out the kernel does NOT have an export name table
# Keeping this here in case it's useful in the future for something else
# Getting function address by name
# IN: r3 == address of name string of function
# IN: LR == address to return to
# OUT: r3 == address of kernel function (or 0 if it could not be found)
getKernelFunctionAddrByName:
mflr r12
# Getting the address of the PE header
lis r4, 0x8004
ori r4, r4, 0x3C
bl get32BitLittleEndian
addis r4, r4, 0x8004 # Final address (RVA + kernel base address)
# Getting the address of the export directory table
addi r4, r4, 0x78
bl get32BitLittleEndian
addis r4, r4, 0x8004 # Final address (RVA + kernel base address)
# Get number of name pointers (and put it in CTR)
mr r8, r4
addi r4, r4, 0x18
bl get32BitLittleEndian
mtctr r4
# Get addresses of the name pointer table and the ordinal table
addi r9, r8, 0x20 # Name pointer table RVA address
addi r4, r8, 0x24 # Ordinal table RVA address
bl get32BitLittleEndian
addis r10, r4, 0x8004 # Ordinal table address
mr r4, r9
bl get32BitLittleEndian
addis r4, r4, 0x8004 # Name pointer table address
# At this point, r4 contains the name pointer table address, and r10 contains the ordinal table address
# r0 (with care), r8, r9 and r11 are free to use. r5, r6 and r7 are used by get32BitLittleEndian,
# r12 is used for our return address, and r3 contains the function name's address.
# We *could* do a binary search here, since this table is alphabetically sorted.
# However, I don't see the marginal speed increase being worth the effort in this case.
# We're also looping backwards here, just because it's easier.
namePointerTableLoop:
# Getting the address to the current name and putting it in r8
mfctr r8
subi r8, r8, 1
mulli r8, r8, 4
add r8, r4, r8
li r11, 0
functionNameLoop:
# Putting characters to compare in r8 and r9, respectively
# r11 is our character offset
lbzx r8, r4, r11
lbzx r9, r3, r11
addi r11, r11, 1
# Checking if characters match, if they don't, move onto the next entry
cmpw cr0, r8, r9
bne checkNextNameEntry
# If both characters are \0, we found our function in the table (as the whole string matches)
cmpwi cr0, r8, 0
beq getCorrespondingOrdinalToNameEntry
# Next character
beq functionNameLoop
checkNextNameEntry:
bdnz namePointerTableLoop
# If we get here, we didn't find the function name in the table.
b kernelFunctionNotFound
getCorrespondingOrdinalToNameEntry:
# At this point, we have our entry to the ordinal table in CTR.
# Convert it to the address of the entry in the ordinal table
mfctr r8
subi r8, r8, 1
mulli r8, r8, 4
add r4, r8, r10
# Get the ordinal at that address in the ordinal table
bl get32BitLittleEndian
# Finally, get the address of the function using the ordinal
mr r3, r4
mtlr r12
b getKernelFunctionAddrByOrdinal
# Getting function address by ordinal
# IN: r3 == ordinal number
# IN: LR == address to return to
# OUT: r3 == address of kernel function (or 0 if it could not be found)
getKernelFunctionAddrByOrdinal:
mflr r12
# Getting the address of the PE header
lis r4, 0x8004
ori r4, r4, 0x3C
bl get32BitLittleEndian
addis r4, r4, 0x8004 # Final address (RVA + kernel base address)
# Getting the address of the export directory table
addi r4, r4, 0x78
bl get32BitLittleEndian
addis r4, r4, 0x8004 # Final address (RVA + kernel base address)
# Making sure the ordinal exists (ordinal is not greater than or equal to export count)
mr r8, r4 # Putting export directory table address into a register unused by get32BitLittleEndian
addi r4, r4, 0x14 # Address of export count
bl get32BitLittleEndian # Get export count
cmpw r3, r4
bgt kernelFunctionNotFound # Ordinal does not exist
# Get address of the export address table, and retrieve the address of our function
subi r3, r3, 1 # Converting ordinal into kernel EAT index
addi r4, r8, 0x1C # Put address of RVA to EAT into r4
bl get32BitLittleEndian # Get RVA to EAT
addis r4, r4, 0x8004 # Address
# Finally, read our address from the export address table and return it
mulli r3, r3, 4 # Multiply index by 4 to get export offset
add r4, r4, r3 # Add export offset to EAT address to get export address
bl get32BitLittleEndian
addis r3, r4, 0x8004 # Finally, the address of our function
mtlr r12
blr
_start:
# Getting address of VdDisplayFatalError function and putting it in CTR to call
li r3, 434 # Ordinal 434 == VdDisplayFatalError
bl getKernelFunctionAddrByOrdinal
mtctr r3 # Put our acquired address into CTR for calling
# Clearing the argument-passing registers so we don't pass anything we don't want
# We don't use these for anything else, so it's safe.
li r4, 0
li r5, 0
li r6, 0
li r7, 0
li r8, 0
li r9, 0
li r10, 0
# Call with E74
lis r3, 0x0001
ori r3, r3, 0x244A
bctr
# Exit code
# Not implemented yet, and not relevant here specifically, but this is probably what I'll do
# Call XexGetModuleHandle(0 (current executable), address to put handle)
# Then call XexUnloadImage(AndExitThread?)(handle address), to exit
# Would that actually work to stop the program? If it does it could save a lot of effort.