Initial commit
This commit is contained in:
commit
5a9c66ea87
2 changed files with 238 additions and 0 deletions
7
build.sh
Executable file
7
build.sh
Executable 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
231
cause360error.s
Normal 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.
|
Loading…
Add table
Reference in a new issue