Author: Chastity White Rose

  • Hacking Minecraft Java Edition with chastehex

    The save files on my Linux machine are here:

    /home/chastity/.minecraft/saves/

    I decided to see if my chastehex program would be any use at editing save files of Minecraft.

    The first step is to use chastecmp (another program I wrote) to compare two files of slight differences.

    The first target was the level.dat file. I had created a new world, collected 3 oak logs from a tree, and saved.

    Debian Linux detected that the level.dat file was actually a gzip archive. Therefore, to get the true data, these commands are required. The first copies it so that it has a “.gz” extension. The second decompresses it.

    cp level.dat file0.gz
    gzip -d file0.gz
    

    Now there is a file named file0 which has the raw data uncompressed. The next step is to load up the game, make a small change and then resave the file. I threw away one oak log so that I had 2 instead of 3. Then I saved the game and ran these commands to get the uncompressed data for the second file.

    cp level.dat file1.gz
    gzip -d file1.gz
    

    I compared the two files with chastecmp:

    chastecmp file0 file1

    And the result was:

    file0 Opened OK
    file1 Opened OK
    00000052 48 CF 
    000003E9 48 CF 
    000007FE 48 CF 
    000009F0 03 02 
    000011D2 69 68 
    000011D3 0E 87 
    000011E5 FB 74 
    0000124B 72 81 
    0000124C EE C7 
    0000124D 99 BA 
    file0 has reached EOF
    

    It appears that address 9F0 contains the amount of oak logs in the first item slot in the game.

    I ran this command to change that byte to 20 hex/32 decimal

    chastehex file0 9F0 20

    The next step is to recompress the data into a level.dat file that the game expects to load.

    gzip -k file0
    cp file0.gz level.dat
    

    Amazingly, when I loaded the game, I did in fact have 32 oak logs. Obviously this process requires multiple steps and is painfully slow, however it proves that my command line tools can hack Minecraft because there is compression but no encryption in the files.

    The level.dat file seems to contain the player’s inventory and other important data. I remember this from experiments with it years ago.

    Warning

    The addresses and what they mean can change wildly as new data is added to the file. For example, I found a village and there were 5 iron ingots in a chest. I repeated the steps above to create two uncompressed files.

    The difference between the files in this case were that I threw 3 of the ingots on the ground and so the number had changed from 5 to 2 in the chastecmp file0 file1 output

    00000F5F 05 02

    So then I ran the command to change the count to 64 (40 hex)

    chastehex file0 F5F 40

    Upon recompressing the file and putting it back in the game folder, I did have 64 ingots. I had previously tried numbers higher than 64 but unfortunately the results were not good. I ended up with only one ingot instead. Therefore, it is good to stick within the limits of what the game expects for the cheating to be successful.

    But because the addresses change as new data gets added to the game, cheating by hex editing is painfully slow on Minecraft. This is best done on a brand new world, both because the data is small and also so that you don’t corrupt worlds you have been playing on longer.

    But I did it as a proof of concept just to see if the programs I wrote can be used to hack Minecraft. The answer is yes, with a little help from gzip for decompression and recompression.

    But what if I told you there was an easier way to cheat at Minecraft Java Edition? You see, the secret still lies with the level.dat file. You don’t actually need to use chastehex, chastecmp, or compression. This is because the file contains the player inventory but not the items they have stored in chests in the world! This allows for item duplication.

    So place the items you want to duplicate in your inventory. Then save the game and backup the file.

    cp level.dat backup.dat

    Then place those items in a chest then save again. Restore the file you backed up earlier.

    cp backup.dat level.dat

    When you reload the game, the files will still be in the chest but your player will also be holding them. This means you can infinitely duplicate any item you can obtain in the game normally by just copying save files repeatedly.

    Why then, did I go through the process of showing how to cheat with chastehex? Because the point is not so much about hacking the game, or what the game is, but it is about testing the programs I wrote. I care more about my C and Assembly programming skills than I do the outcome of a game.

    The point is not whether you win or lose the game. The point is that the game was made by humans and can be broken by humans. Sometimes, as in Castle of the Winds or Cave Story, hacking with a hex editor is the most reliable method. In a game like Minecraft, sometimes cheating is easier because of player and world data being in completely separate files.

  • MIPS Assembly chastelib

    I have been learning the Assembly language of MIPS recently, just for fun. I have been learning from this book:

    I learned enough to translate my Intel functions into the language of MIPS. My code runs in both the spim and mars simulators.

    Also, this reference was helpful for understanding the division instructions which were not covered by the book written by Robert Winkler:

    I don’t expect to have much use for these functions, but theoretically, I could write nearly anything with them if I put enough time with them. Most of the time I will stick with the Intel Assembly because it runs natively on my computer. I am very glad these simulators exist and were so easy to use. Unfortunately I haven’t found the equivalent simulators I was hoping to for 6502.

    # This is the source of the MIPS version of chastelib
    # The four basic functions have been translated from Intel x86 Assembly
    # They are as follows
    #
    # putstring (prints string pointed to by $s0 register)
    # intstr (converts integer in $s0 register to a string)
    # putint (prints integer in $s0 register by use of the previous two functions)
    # strint (converts a string pointed to by $s0 register into an integer in $s0)
    #
    # Most importantly, the intstr and strint functions depend on a global variable (radix)
    # In fact, these two functions are the foundation of everything.
    # They can convert to and from any radix from 2 to 36
    
    .data
    title: .asciiz "A test of Chastity's integer and string conversion functions.\n"
    
    # test string of integer for input
    test_int: .asciiz "10011101001110011110011"
    
    #this is the location in memory where digits are written to by the putint function
    int_string: .byte '?':32
    int_newline: .byte 10,0
    radix: .byte 2
    int_width: .byte 4
    
    .text
    main:
    
    la $s0,title
    jal putstring
    
    li $s0,0 #we will load the $s0 register with the number we want to convert to string
    
    loop:
    jal putint
    addi $s0,$s0,1
    blt $s0,16,loop
    
    la $s0,test_int # convert string to integer
    jal strint
    
    li $t0,10    #change radix
    sb $t0,radix
    
    li $t0,8    #change width
    sb $t0,int_width
    
    jal putint
    
    li   $v0, 10     # exit syscall
    syscall
    
    
    putstring:
    li $v0,4      # load immediate, v0 = 4 (4 is print string system call)
    move $a0,$s0  # load address of string to print into a0
    syscall
    jr $ra
    
    
    #this is the intstr function, the ultimate integer to string conversion function
    #just like the Intel Assembly version, it can convert an integer into a string
    #radixes 2 to 36 are supported. Digits higher than 9 will be capital letters
    
    intstr:
    
    la $t1,int_newline # load target index address of lowest digit
    addi $t1,$t1,-1
    
    lb $t2,radix     # load value of radix into $t2
    lb $t4,int_width # load value of int_width into $t4
    li $t3,1         # load current number of digits, always 1
    
    digits_start:
    
    divu $s0,$s0,$t2 # $s0=$s0/$t2 (divide s0 by the radix value in $t2)
    mfhi $t0        # $t0=remainder of the previous division
    blt $t0,10,decimal_digit
    bge $t0,10,hexadecimal_digit
    
    decimal_digit: # we go here if it is only a digit 0 to 9
    addi $t0,$t0,'0'
    j save_digit
    
    hexadecimal_digit:
    addi $t0,$t0,-10
    addi $t0,$t0,'A'
    
    save_digit:
    sb $t0,($t1) # store byte from $t0 at address $t1
    beq $s0,$0,intstr_end
    addi $t1,$t1,-1
    addi $t3,$t3,1
    j digits_start
    
    intstr_end:
    
    li $t0,'0'
    prefix_zeros:
    bge $t3,$t4,end_zeros
    addi $t1,$t1,-1
    sb $t0,($t1) # store byte from $t0 at address $t1
    addi $t3,$t3,1
    j prefix_zeros
    end_zeros:
    
    move $s0,$t1
    
    jr $ra
    
    #this function calls intstr to convert the $s0 register into a string
    #then it uses a system call to print the string
    #it also uses the stack to save the value of $s0 and $ra (return address)
    
    putint:
    sw $ra,0($sp)
    sw $s0,4($sp)
    jal intstr
    #print string
    li $v0,4      # load immediate, v0 = 4 (4 is print string system call)
    move $a0,$s0  # load address of string to print into a0
    syscall
    lw $ra,0($sp)
    lw $s0,4($sp)
    jr $ra
    
    
    
    
    
    
    
    
    
    
    
    strint:
    
    move $t1,$s0 # copy string address from $s0 to $t1
    li $s0,0
    
    lb $t2,radix     # load value of radix into $t2
    
    read_strint:
    lb $t0,($t1)
    addi $t1,$t1,1
    beq $t0,0,strint_end
    
    #if char is below '0' or above '9', it is outside the range of these and is not a digit
    blt $t0,'0',not_digit
    bgt $t0,'9',not_digit
    
    #but if it is a digit, then correct and process the character
    is_digit:
    and $t0,$t0,0xF
    j process_char
    
    not_digit:
    #it isn't a digit, but it could be perhaps and alphabet character
    #which is a digit in a higher base
    
    #if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    blt $t0,'A',not_upper
    bgt $t0,'Z',not_upper
    
    is_upper:
    
    sub $t0,$t0,'A'
    addi $t0,$t0,10
    j process_char
    
    not_upper:
    
    #if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    blt $t0,'a',not_lower
    bgt $t0,'z',not_lower
    
    is_lower:
    
    sub $t0,$t0,'a'
    addi $t0,$t0,10
    j process_char
    
    not_lower:
    
    #if we have reached this point, result invalid and end function
    #this is only reached if the byte was not a valid digit or alphabet character
    j strint_end
    
    process_char:
    
    bgt $t0,$t2 strint_end #;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mul $s0,$s0,$t2 # multiply $s0 by the radix
    add $s0,$s0,$t0     # add the correct value of this digit
    
    j read_strint # jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    jr $ra
    
    
  • The Power of a Smile

    I work at Walmart, and I am the one famous for smiling all the time. This behavior is non-characteristic of autistic people like me, as far as I am aware. The truth is that I use it as a mask to hide what I am really thinking. I use a sense of humor to act like I am not bothered by the offensive things people say and do. Surviving in society and working a job requires me to be an actor. I dislike this because it feels dishonest, and yet I know I can’t say what I am really thinking about because it has nothing to do with work, nor is it socially acceptable to say.

    I very clearly remember multiple instances when I smiled at someone, and then they acted really uncomfortable, and one even ran away. I have realized I have the power to get rid of people by smiling at them in a way I have been told is creepy. And yet, I don’t actually know how to smile like a human.

    In any case, I have come to see a smile as an illusion that society forces us to maintain, or else there is severe punishment. If I were to show my real feelings towards certain people, I would probably be fired or even attacked. I think that if I ever have a real smile, it is when nobody is watching and I am alone. I will remain mysterious because it makes me safe, and it is fun to annoy people with the power of a smile.

    “Just because you see a smile, don’t think you know what’s going on underneath. A smile is a valuable tool, my dear! It inspires your friends, keeps your enemies guessing, and ensures that no matter what comes your way, you’re the one in control.” – Alastor the Radio Demon

  • Writing to video RAM

    One of the reasons DOS is the best platform for learning Intel Assembly language in my opinion is that it doesn’t prevent you from writing directly to video memory. People are unaware how much operating systems like Windows, and, to a lesser extent, Linux place limits on what you are allowed to do. The idea is that most people are not smart enough to be trusted with arbitrarily writing to video RAM, devices, etc.

    But that is where DOSBox comes in. Since it runs DOS inside an emulator, there is no danger. The program I am going to show you today is long and complicated but it does something that I can’t even do on Linux, write directly to video memory in multiple colors.

    Here is the source code that made all of that possible! I tested it to assemble with either FASM or NASM. If you assemble it and run it in DOSBox, or even a real DOS system, you will get something that looks like the picture above.

    org 100h
    
    main:
    
    ;set up the extra segment at the beginning of the program
    ;to point to the video memory segment in DOS text mode
    mov ax, 0xB800
    mov es, ax ; Or mov ds, ax
    
    
    ;80 columes times 25 rows is 2000 chars
    ;but since each character is two bytes
    ;4000 is the number of bytes to erase
    ;whatever character we write in this loop will fill the whole screen!
    
    mov bx,0
    screen_clear:
    mov [es:bx],word 0x0403
    add bx,2
    cmp bx,4000
    jnz screen_clear
    
    mov ax,title  ;the string we intend to write to video RAM
    mov ch,0x0F   ;the character attribute
    mov dx,0x0218
    call putstring_vram
    
    mov ax,v_str  ;the string we intend to write to video RAM
    mov ch,0x70   ;the character attribute
    mov dx,0x0401
    call putstring_vram
    
    ;set the starting attribute for characters and location
    mov ch,0x01   ;the character attribute
    mov dx,0x0501 ;x,y position of where text should start on screen
    
    loop_vram:
    cmp ch,0x10
    jz loop_vram_end
    mov ax,v_str  ;the string we intend to write to video RAM
    call putstring_vram
    add dx,0x100
    inc ch
    jmp loop_vram
    loop_vram_end:
    
    mov ax,4C00h
    int 21h
    
    title db 'Chastity Video RAM Demonstration!',0
    v_str db 'Hello World! This string will be written to video RAM using Assembly language!',0
    
    ;Unlike previous functions I wrote that use DOS interrupts to write text to the screen
    ;this one makes use of several registers which are not meant to be preserved
    ;registers ax,cx,and dx must be set before calling this function
    
    ;ax = address of string to write
    ;bx = copied from ax and used to index the string
    ;cx = used for character attribute(ch) and value(cl)
    ;dx = column(x pos) and row(y pos) of where string should be printed
    
    ;For this routine, I chose to copy the dx register to memory locations for clarity
    ;Yes, it wastes some bytes but at least I can read it as I am familiar with x,y coordinates
    ;Most importantly, the dx register is never modified in this function
    ;This is important because the main program may need to modify it in a loop
    ;For writing data in consecutive rows (e.g. integer sequences)
    
    x db 0
    y db 0
    
    putstring_vram:
    
    mov bx,ax             ;copy ax to bx for use as index register
    
    ;get x and y positions from each byte of dx register
    mov [x],dl
    mov [y],dh
    
    mov ax,80  ;set ax to 80 because there are 80 chars per row in text mode
    mul byte [y]    ;multiply with the y value
    mov byte [y],0  ;zero the y byte so we can add a 16 bit x value to ax
    add ax, word [x]
    
    shl ax,1 ;shift left once to account for two bytes per character
    
    mov di,ax ;we will use di as our starting output location
    
    putstring_vram_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx],byte 0                 ;compare this byte with 0
    jz putstring_vram_strlen_end    ;if comparison was zero, jump to loop end because we have found the length/end of string
    mov cl,[bx]                     ;mov this character to cl
    mov [es:di],cx                  ;mov character and attribute set in ch(before calling this function) to extra_segment+di
    add di,2                        ;each character contains two bytes (ASCII+Attribute). We must add two here.
    inc bx                          ;increment bx to point to next character
    jmp putstring_vram_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_vram_strlen_end:
    
    ret
    
  • arithmetic by bitwise operators

    I have started a new programming library in C which simulates arithmetic using only bitwise operators. This is purely for computer science purposes and to prove that it can be done. The hardest part was getting the division function working correctly. I can prove that these functions work with another program I wrote, but I really want to record a video sometime to show the process of how these work.

    For now, here is the source of the 4 arithmetic functions and some global variables used in the division function.

    /*
     bitlib.h
    
     This library simulates the four arithmetic functions: ( addition, subtraction, multiplication, and division )
     Using only bitwise operations: ( AND, OR, XOR, SHL, SHR )
     
     Most of the time I would not need to do this, however, there exist applications where this information may prove useful.
     
     - Programming ARM CPUs which don't have a division instruction.
     - Arbitary Precision Arithmetic involving thousands of digits.
    
    */
    
    int add(int di,int si)
    {
     while(si!=0)
     {
      int ax=di;
      di^=si;
      si&=ax;
      si<<=1;
     }
     return di;
    }
    
    
    int sub(int di,int si)
    {
     while(si!=0)
     {
      di^=si;
      si&=di;
      si<<=1;
     }
     return di;
    }
    
    
    
    int mul(int di,int si)
    {
     int ax=0;
     while(si!=0)
     {
      if((si&1)!=0){ax=add(ax,di);}
      di<<=1;
      si>>=1;
     }
     return ax;
    }
    
    /*
    this division function returns the quotient, but also stores the remainder of division in a global variable
    */
    
    int sign_bit=1<<((sizeof(int)<<3)-1); /*used to extract the most significant bit during division function*/
    
    int mod=0; /*to store the modulus/remainder of the division function*/
    
    int bitdiv(int di,int si)
    {
     int ax=0,bx=0,cx=1;
     if(si==0){return 0;} /*division by zero is invalid*/
    
     while(cx!=0)
     {
      ax<<=1;
      bx<<=1;
      if(di&sign_bit){bx|=1;}
      di<<=1;
      
      if(bx>=si)
      {
       bx=sub(bx,si);
       ax|=1;
      }
     
      cx<<=1;
     }
    
     mod=bx;
     return ax;
    }