News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Alchemic

Pages: [1]
1
Programming / Re: Battles background animations in Earthbound
« on: July 02, 2019, 06:07:43 pm »
It is indeed possible.

The size of the battle "letterbox" is controlled by the "enemy battle groups pointer table" at 0x10C60D to 0x10D52C*. Each entry in that table has eight bytes, and the last byte of each entry controls the letterbox size for that battle group (00-03, with 00 being "full screen visible").

* A lot of EarthBound's hacking documentation uses addresses with the 0x200-byte copier header included, and would give the range as 0x10C80D to 0x10D72C instead. Use whichever is appropriate to your ROM (hopefully the former).

So the slow, laborious approach would be to update each entry with a last byte of 00 - but we can do better, by updating the code that reads the letterbox size instead:

Code: [Select]
...
C2/48CC: AD 8C 4A     LDA $4A8C                 A = [1F 23] battle entry number
C2/48CF: 0A           ASL
C2/48D0: 0A           ASL
C2/48D1: 0A           ASL
C2/48D2: 18           CLC
C2/48D3: 69 07 00     ADC #$0007
C2/48D6: AA           TAX
C2/48D7: BF 0D C6 D0  LDA $D0C60D,X
C2/48DB: 29 FF 00     AND #$00FF
C2/48DE: 85 29        STA $29
...

Change the 29 FF 00 (AND #$00FF) at 0x248DB* to A9 00 00 (LDA #$0000), and it will use the constant 0 instead of the byte from the table. (On review, 29 00 00 (AND #$0000) would also work, if you wanted to change as few bytes as possible.)

* Or 0x24ADB, for a headered ROM.

2
ROM Hacking Discussion / Re: Handling possible text compression
« on: September 21, 2018, 01:36:17 am »
I did some digging around in DW2 a few years back. My notes are kind of a mess, but to summarize:
  • Text is built up out of pieces that are five or ten bits in length.
  • The underlying characters for each piece are at 0xB49B - 0xB686. Abbreviated TBL below.
  • The piece lengths are at 0xB44B - 0xB49A. One nybble per piece.
  • You can think of the pieces as one table with 160 entries, or five tables with 32 entries each.
  • The 1st table handles bit patterns 00000 through 11011.
  • The 2nd table handles 11100 xxxxx, the 3rd 11101 xxxxx, the 4th 11110 xxxxx, the 5th 11111 xxxxx.
  • The text pointers are at 0xB762 - 0xB7C1.
  • Each pointer points at a blob of 16 concatenated strings.
  • Compressed text is at 0x14010 - 0x17FE6, and a bit more at 0xB7C2 to 0xBE0F.
Code: [Select]
TBL for 0xB49B:
00-09 = 0-9
0A-23 = a-z
24-3D = A-Z
3E-57 = a-z again
58=A
59=(space)
5A=[SUN]
5B=[STAR]
5C=[MOON]
5D=[DROP]
5E=[HEART]
5F=(space)
60=(space)
61='
62="
63=->
64="
65='
66='
67='
68=.'
69=,
6A=-
6B=.
6C=&
6D=(space)
6E=?
6F=!
70=;

Text-dumping code in Python

3
Eek, that was a really old version of "soul_lairs.txt".

I've updated the links on the Soul Blazer page on Data Crystal with newer files. (Most are unchanged except for removing the copier header from addresses, but the lair file has a lot more stuff now.)

Byte 09 is an index into a pointer table at 0x152E6.
  • Let's consider... lair 008.
  • Byte 09 is 0x05 in that row.
  • 0x152E6 + (2 * 0x05) = 0x152E6 + 0x0A = 0x152F0.
  • The bytes at 0x152F0 are [76 D3] = $D376 in the same bank = 0x15376.
  • The text at 0x15376 is "The bridge guard" (using Soul Blazer's text encoding).
I think that only affects the resurrection message, though.

Changing how NPCs get resurrected involves changing their behaviour. Here's some of my notes on NPCs/enemies:
The behaviour pointer is to SNES assembly code (OK), but with the COP instruction repurposed to create a custom control code language (uhoh). Most of the NPC code starts with [02 14 XX XX YY YY], which branches to YYYY if lair XXXX is sealed.

Looking back over those files, some mention map numbers, so here's some more stuff:
I should probably stop here, before this post gets big enough to start rampaging Godzilla-style. If you have any questions, please let me know.

4
Some rather strained connections, here mostly for the sake of completeness:
  • "Doromi" reminded me of "dormir" (French verb, "to sleep"), which is one of several descendents from the Latin verb "dormio" (Wiktionary link).
  • Dolomiaea is a genus of flowering plants in the daisy family. The only thing I could find related to smoke/gas from it was a mention of the roots of dolomiaea macrocephala being burned as incense. (There doesn't seem to be much information about this genus anywhere.)

5
Newcomer's Board / Re: How to start editing graphics with compression?
« on: November 19, 2012, 07:21:19 pm »
Robotrek uses Quintet's standard LZSS implementation (also used by both ActRaisers, Soul Blazer and Illusion of Gaia - but not Terranigma, sadly).

Quintet LZSS decompressor
Quintet LZSS compressor

As for the location of compressed graphics, the Robotrek ROM map on Data Crystal may prove useful.

6
Personal Projects / Re: Magic Kids; Scubbing McD's from M.C. Kids
« on: November 17, 2012, 12:32:57 am »
As requested, here's a compressor. Some observations:
  • It's not quite as space-efficient as whatever the original programmers had, so if you recompress the game's text without any modifications, you'll get a result a few bytes larger than the original compressed data.
  • Mixed up some control codes in my previous post: 0x01 clears the text window, while 0x00 ends a string within a block.

Python compressor source:

Code: [Select]
# M.C. Kids Text Compressor
# Written by Alchemic
# 2012 Nov 16
#
# This code uses python-bitstring version 2.2.0:
# http://code.google.com/p/python-bitstring/

from __future__ import division

import sys
import bitstring





def compress(inBytes, sourceArgSize, lengthArgSize):
    # Define some useful constants.
    BIT_PASTCOPY = 0
    BIT_LITERAL = 1

    # Prepare for compression.
    currentIndex = 0
    endIndex = len(inBytes)
    output = bitstring.BitArray()
    output += bitstring.pack('uint:4', sourceArgSize)
    output += bitstring.pack('uint:4', lengthArgSize)

    # Main compression loop.
    while currentIndex < endIndex:
        bestSource = 0
        bestLength = 0

        # Compare what's coming up to what we've most recently seen.
        searchLimit = min(
            currentIndex,
            (1 << sourceArgSize) - 1
        )
        for i in xrange(1, searchLimit):
            # Don't look too far ahead at what's coming up:
            # - No further than can be encoded in one command.
            # - Not past the end of the input.
            lookaheadLimit = min(
                (1 << lengthArgSize) - 1 + 3,
                endIndex - currentIndex
            )

            # Count how many sequential bytes match (possibly zero).
            currentLength = 0
            for j in xrange(lookaheadLimit):
                if inBytes[currentIndex - i + j] == inBytes[currentIndex + j]:
                    currentLength += 1
                else:
                    break

            # Keep track of the largest match we've seen.
            if currentLength > bestLength:
                bestSource = i
                bestLength = currentLength

        # Write the next command.
        if bestLength >= 3:
            output += bitstring.pack('uint:1', BIT_PASTCOPY)
            output += bitstring.pack('uint:n=v', n = sourceArgSize, v = bestSource)
            output += bitstring.pack('uint:n=v', n = lengthArgSize, v = bestLength - 3)
            currentIndex += bestLength
        else:
            output += bitstring.pack('uint:1', BIT_LITERAL)
            output += bitstring.pack('uint:8', inBytes[currentIndex])
            currentIndex += 1

    # Write the terminating bits.
    output += bitstring.pack('uint:1', BIT_PASTCOPY)
    output += bitstring.pack('uint:n=v', n = sourceArgSize, v = 0)
    output += bitstring.pack('uint:n=v', n = lengthArgSize, v = 0)

    # Return the compressed data.
    return output.tobytes()





if __name__ == "__main__":

    # Check for incorrect usage.
    argc = len(sys.argv)
    if argc < 2 or argc > 4:
        sys.stdout.write("Usage: ")
        sys.stdout.write("{0:s} ".format(sys.argv[0]))
        sys.stdout.write("<inFile> [outFile] [outOffset]\n")
        sys.exit(1)

    # Copy the arguments.
    inFile = sys.argv[1]
    outFile = None
    if argc == 3 or argc == 4:
        outFile = sys.argv[2]
    outOffset = 0
    if argc == 4:
        outOffset = int(sys.argv[3], 16)

    # Open, read and close the input file.
    inStream = open(inFile, "rb")
    inBytes = bytearray(inStream.read())
    inStream.close()

    # Compress the data.
    compressedOptions = []
    for sourceArgSize in xrange(10, 12):
        for lengthArgSize in xrange(3, 6):
            sys.stdout.write("Compressing: {0:d},{1:d}".format(sourceArgSize, lengthArgSize))
            thisOption = compress(inBytes, sourceArgSize, lengthArgSize)
            sys.stdout.write(" = {0:d} bytes\n".format(len(thisOption)))
            compressedOptions.append(thisOption)
    outBytes = min(compressedOptions, key = len)
    sys.stdout.write("Done.\n\n")

    # Write the compressed output, if appropriate.
    if outFile is not None:
        # Mode r+b gives an error if the file doesn't already exist.
        open(outFile, "a").close()
        outStream = open(outFile, "r+b")
        outStream.seek(outOffset)
        outStream.write(outBytes)
        outStream.close()

    # Report statistics on the data.
    sys.stdout.write("Uncompressed size: 0x{0:X} ({0:d}) bytes\n".format(len(inBytes)))
    sys.stdout.write("Compressed size: 0x{0:X} ({0:d}) bytes\n".format(len(outBytes)))
    sys.stdout.write("Ratio: {0:f}\n".format(len(outBytes) / len(inBytes)))

    # Exit.
    sys.exit(0)

7
Personal Projects / Re: Magic Kids; Scubbing McD's from M.C. Kids
« on: November 14, 2012, 10:05:06 pm »
I did some poking around, and I think I've figured out the text compression. It's a kind of Lempel-Ziv:

  • 4 bits: size of the "source" argument for pastcopies, in bits
  • 4 bits: size of the "length" argument for pastcopies, in bits
  • Stream of compressed bits; read each byte most significant bit to least

The stream of compressed bits consists of a sequence of these two commands:

Pastcopy
  • 0 (source) (length)
  • Copies previously-decompressed data.
  • The number of bits used by "source" and "length" is specified in the first byte of compressed data.
  • The "source" is how many bytes ago to copy from, and a source of 0 indicates the end.
  • The "length" is the number of bytes to copy, less three (so a length of 0 copies 3 bytes, 1 copies 4, etc).

Literal
  • 1 nnnnnnnn
  • Outputs one literal byte.

A sample decompression from 0x4AAC:

Code: [Select]
B5 82 41 23 10 A8 15 52 D1 69 B9 C8 00 0C 16 DB CC 82 A7 70 ...

1011 = Pastcopy sources are eleven bits long
0101 = Pastcopy lengths are five bits long

1 00000100 = '\x04'
1 00000100 = '\x04'
1 00011000 = '\x18'
1 00001010 = '\x0A'
1 00000010 = '\x02'
1 01010100 = 'T'
1 01101000 = 'h'
1 01101001 = 'i'
1 01110011 = 's'
1 00100000 = ' '
0 00000000011 00000 = "is "
1 01101101 = 'm'
1 01111001 = 'y'
1 00100000 = ' '
1 01010011 = 'S'
1 01110000 = 'p'
...

The letters are ASCII, and there are control codes in the text; 0x01 ends a string, 0x02 is newline, not sure about other codes.

Some known blocks of compressed text (end addresses inclusive):
  • 0x4010 to 0x4608
  • 0x4609 to 0x4AAB
  • 0x4AAC to 0x5082
I'm not sure if there are more or not.

Python decompressor source:
Code: [Select]
# M.C. Kids Text Decompressor
# Written by Alchemic
# 2012 Nov 13
#
# This code uses python-bitstring version 2.2.0:
# http://code.google.com/p/python-bitstring/

import sys
import bitstring





def decompress(romFile, startOffset):
    # Define some useful constants.
    BIT_PASTCOPY = 0
    BIT_LITERAL = 1

    # Open the ROM.
    romStream = bitstring.ConstBitStream(filename=romFile)
    romStream.bytepos += startOffset

    # Allocate storage for the decompressed output.
    decomp = bytearray()

    # Read the first byte.
    # (It specifies the size of pastcopy's two arguments.)
    copySourceSize = romStream.read('uint:4')
    copyLengthSize = romStream.read('uint:4')

    # Main decompression loop.
    while True:
        nextCommand = romStream.read('bool')

        if nextCommand == BIT_PASTCOPY:
            # 0: Pastcopy case.
            copySource = romStream.read(copySourceSize).uint
            copyLength = romStream.read(copyLengthSize).uint
            copyLength += 3

            # A copy source of 0 indicates the end.
            if copySource == 0:
                break
           
            for i in xrange(copyLength):
                decomp.append(decomp[-copySource])

        elif nextCommand == BIT_LITERAL:
            # 1: Literal case.
            literalByte = romStream.read('uint:8')
            decomp.append(literalByte)

    # Calculate the end offset.
    romStream.bytealign()
    endOffset = romStream.bytepos

    # Return the decompressed data and end offset.
    return (decomp, endOffset)





if __name__ == "__main__":

    # Check for incorrect usage.
    argc = len(sys.argv)
    if argc < 3 or argc > 4:
        sys.stdout.write("Usage: ")
        sys.stdout.write("{0:s} ".format(sys.argv[0]))
        sys.stdout.write("<romFile> <startOffset> [outFile]\n")
        sys.exit(1)

    # Copy the arguments.
    romFile = sys.argv[1]
    startOffset = int(sys.argv[2], 16)
    outFile = None
    if argc == 4:
        outFile = sys.argv[3]

    # Decompress the data.
    outBytes, endOffset = decompress(romFile, startOffset)

    # Write the decompressed output, if appropriate.
    if outFile is not None:
        outStream = open(outFile, "wb")
        outStream.write(outBytes)
        outStream.close()

    # Report the size of the compressed data and last offset.
    sys.stdout.write("Original compressed size: 0x{0:X} ({0:d}) bytes\n".format(endOffset - startOffset))
    sys.stdout.write("Last offset read, inclusive: {0:X}\n".format(endOffset - 1))

    # Exit.
    sys.exit(0)

8
There's an enemy-related data table at 0x3F591 to 0x3FB42 (with 18-byte entries), though I'm not sure if it has enemy HP in it. It DOES have the amount of experience points, though.

The table (spoilered for size):
Spoiler:
Code: [Select]
------------------------------------------------------------------------
Robotrek Enemy Data
------------------------------------------------------------------------

0x3F591 to 0x3FB42, 18-byte entries.
Asterisks before the name indicate unresolved ambiguity.

02 to 03 = Experience, in units of 0.1 (binary-coded decimal)



00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11
-----------------------------------------------------
05 00 01 00 00 00 02 00 60 09 02 00 60 09 00 00 00 00   Mine
1E 00 07 00 14 18 30 00 30 09 32 28 40 09 00 00 40 00   Turbo
05 00 01 00 00 00 03 00 60 09 03 00 60 21 00 00 00 00   Mushroom
1E 00 08 00 1E 18 3B 00 30 09 38 24 5D 21 00 00 00 00   Poison
12 00 03 00 0A 10 0F 00 40 0C 08 22 6A 23 81 00 20 00   Spider (0.3 exp)
1E 00 09 00 1E 18 2E 00 20 0C 2B 24 48 23 81 00 20 00   Spider (0.9 exp)
05 00 01 00 00 00 08 00 50 0C 02 22 56 23 21 00 00 00   Gel
28 00 08 00 22 18 3A 00 40 0C 36 24 4C 23 60 00 0C 00   Gelgel
32 00 14 00 28 1C 3F 00 40 09 37 02 52 21 00 00 00 00   Bosstoad
0A 00 02 00 00 10 08 00 30 09 05 00 50 21 20 00 00 00   Rushbird
1E 00 10 00 1E 30 52 00 20 09 4E 22 3F 21 20 00 00 00   Quickbird
1A 00 06 00 0C 00 1C 00 40 09 1E 0C 40 21 00 00 0B 00   Mole
30 00 10 00 1C 10 20 00 40 09 2E 30 4B 81 00 00 0B 00   Powermole
41 00 16 00 44 28 57 0C 20 81 54 00 40 21 81 00 00 00   Tackler
14 00 07 00 0E 10 22 0E 50 81 24 16 60 44 88 00 30 00   Plasmoke
1E 00 07 00 14 10 29 0E 50 81 2C 12 60 42 88 00 30 00   Elesmoke
14 00 06 00 05 1E 22 10 30 41 22 00 30 41 20 00 43 00   Monk
1E 00 08 00 14 08 28 26 6E 81 29 00 60 21 81 00 20 00   Mummy
28 00 09 00 19 08 2D 00 60 0B 29 32 6B 81 81 00 20 00   Franken
0A 00 06 00 14 18 24 34 4B 21 24 14 64 43 81 00 20 00   Ho Ho
32 00 11 00 14 28 37 28 30 23 34 00 30 21 00 00 00 00   Gator
38 00 13 00 2B 1E 44 0C 80 81 42 00 60 22 09 03 44 00   Gunrobot
3C 00 13 00 2B 1E 42 00 60 23 44 0E 80 22 09 03 44 00   CannonX
14 00 11 00 1E 28 3E 00 20 09 36 34 3F 81 81 01 00 00   Biolion
64 00 00 03 96 78 00 00 10 00 00 00 10 00 F0 03 00 00   Shell
14 00 09 00 3C 20 4A 00 30 23 46 24 1C 81 00 00 00 00   Urchin
1E 00 12 00 3C 38 48 00 10 23 48 00 10 23 C0 03 08 00   Luckstar
1E 00 09 00 28 22 52 16 30 44 4A 22 3A 21 42 03 00 00   Angler
1E 00 10 00 28 28 4D 0E 20 09 4C 00 50 21 00 00 00 00   *********************** Cmdr.
1E 00 10 00 28 28 4A 00 40 21 4A 22 4E 81 00 00 00 00   *********************** Cmdr.
3C 00 15 00 3C 20 50 0C 30 09 00 00 30 81 C0 00 41 00   Octopus
3C 00 15 00 3C 32 57 0C 10 81 55 00 10 22 08 00 44 00   Pumpy
50 00 18 00 46 5F 82 0C 10 81 82 00 10 22 08 00 44 00   Bumpy
78 00 20 00 50 4A 78 28 38 09 50 26 3E 81 48 00 20 00   Faceman
96 00 24 00 78 50 A5 28 38 09 96 28 38 23 48 00 20 00   Bigface
14 00 20 00 46 46 5A 06 12 81 92 10 10 41 C0 03 00 00   Redpixy
1E 00 22 00 5A 50 82 30 1F 81 9C 14 10 43 C0 03 00 00   Whitepixy
1E 00 22 00 5A 62 8C 32 1F 81 A6 12 10 42 C0 03 00 00   Goldpixy
50 00 18 00 73 3E 62 00 30 21 60 34 3F 81 C0 00 23 00   Knight
64 00 22 00 7D 54 88 00 30 21 88 26 3F 81 C0 00 23 00   Master
96 00 28 00 80 5A 9E 00 40 41 94 02 42 21 00 00 43 00   Gagarian
78 00 30 00 6E 6E 96 00 10 09 9C 00 10 21 C0 01 10 00   Ninja
08 00 02 00 00 02 02 00 80 09 05 00 80 09 00 00 00 00   *********************** Cmdr.
19 00 03 00 0A 05 0F 00 60 09 14 00 60 09 00 00 0C 00   *********************** Cmdr.
0A 00 03 00 05 05 1C 00 60 21 1C 00 60 21 00 00 00 00   *********************** Cmdr.
0A 00 03 00 05 05 1C 00 60 23 1C 00 60 23 00 00 00 00   *********************** Cmdr.
0A 00 03 00 0A 18 2A 28 40 09 2E 0C 40 09 00 00 00 00   *********************** Cmdr.
1E 00 04 00 1C 18 2E 28 20 09 38 0C 40 09 00 00 00 00   *********************** Cmdr.
1E 00 05 00 14 1E 3A 28 20 21 3E 00 20 22 00 00 00 00   *********************** Cmdr.
1E 00 04 00 14 1E 3E 00 20 23 3E 00 20 23 00 00 00 00   *********************** Cmdr.
1E 00 07 00 22 18 50 28 54 09 55 0C 60 09 40 00 00 00   *********************** Cmdr.
32 00 09 00 30 28 54 28 24 09 5A 0C 40 09 40 00 21 00   *********************** Cmdr.
2D 00 10 00 24 20 59 28 44 21 5C 00 40 22 40 00 00 00   *********************** Cmdr.
2C 00 10 00 1E 20 5C 14 40 41 5C 10 40 43 40 00 00 00   *********************** Cmdr.
05 00 04 00 14 14 3C 00 80 81 08 02 42 81 00 00 00 00   Minicom (0.4 exp)
0A 00 05 00 28 32 6E 00 80 81 3C 30 4F 81 00 00 00 00   Minicom (0.5 exp)
0A 00 06 00 50 50 8C 00 80 81 5A 32 4F 81 00 00 00 00   Minicom (0.6 exp)
32 00 16 00 48 46 82 00 20 09 84 24 28 09 40 00 00 00   *********************** Big Fan
32 00 16 00 46 44 88 00 30 23 8A 00 30 21 40 00 00 00   *********************** Big Fan
32 00 16 00 3C 44 8C 00 30 23 8B 00 30 21 40 00 00 00   *********************** Big Fan
00 00 00 00 00 00 00 00 80 00 00 00 80 00 00 00 00 00   Beeper
0A 00 01 00 00 00 00 02 32 81 00 00 80 00 00 00 00 00   *********************** Cmdr.
0A 00 20 00 50 28 BE 28 18 21 C0 00 10 21 C0 03 00 00   Masker (2.0 exp)
0A 00 26 00 64 14 78 00 10 21 78 00 10 21 C0 03 00 00   Masker (2.6 exp)
0A 00 28 00 64 1C A0 24 1C 23 A5 00 10 23 C0 03 00 00   Masker (2.8 exp)
0A 00 20 00 50 28 BE 28 18 21 C0 28 18 21 C0 03 00 00   Beret (2.0 exp)
0A 00 25 00 78 14 78 00 10 23 78 26 1A 23 C0 03 00 00   Beret (2.5 exp)
0A 00 26 00 64 1E A5 0C 10 81 AA 00 10 81 C0 01 00 00   Beret (2.6 exp)
05 00 01 00 00 00 00 00 80 00 00 00 80 00 00 00 00 00   -
C8 00 00 02 16 05 1E 0C 40 09 0A 00 40 21 81 01 00 00   Meta Crab
0A 00 01 00 0A 00 19 28 24 09 00 00 80 00 10 00 00 00   Smallcrab
90 01 00 05 28 18 2A 26 4E 81 36 12 40 42 81 01 00 00   Mamurana
58 02 00 07 40 28 50 0C 20 81 58 00 20 22 E0 00 0B 00   Big Eye
14 05 00 09 73 44 7E 28 14 21 8A 00 40 23 C0 00 0B 00   Blacktank
F0 0A 00 09 96 55 9E 00 10 21 A4 28 48 81 81 00 00 00   Gateau (90.0 exp)
20 03 50 00 78 46 8E 34 4E 22 8E 00 40 00 81 00 00 00   Soldier
50 14 00 00 96 5F B6 0C 20 81 AA 26 2A 81 C0 00 00 00   Gateau (Final boss)
D0 07 00 05 82 50 96 30 1F 22 A0 00 10 23 81 03 00 00   De Rose
60 09 00 04 87 46 A0 16 10 44 96 26 2B 22 C0 03 22 00   Bugbug (Second battle)
1C 02 00 03 50 26 44 28 3C 21 5C 00 30 23 C0 01 12 00   Papamecha
BC 02 00 04 6E 2A 82 16 10 44 7E 22 4A 22 C0 00 22 00   Bugbug (First battle)
-----------------------------------------------------

Since PAR codes are just six hex digits of address followed by two hex digits of value, the Robotrek PAR code guide on GameFAQs indicates memory addresses where robot stats are stored: Robot #1's Energy is at 7E068A-B, #1's Energy Max is at 7E0690-1, etc. Looking for code that writes to these addresses might be helpful.

One caveat: I think there's a typo in it for Robot #3's Energy stat (it's probably at 7E068E-F instead of 7E06AE-F).

9
Newcomer's Board / Re: Changing class equipment selection in FF3j
« on: March 14, 2012, 01:05:31 pm »
Regarding the 0x80 bit for armor:
  • Consider the Headband from the first post, with the "who-can-equip" value of 0x9E (1001 1110).
  • If you ignore the 0x80 bit of this value, you get 0x1E (0001 1110).
  • The 1E entry in the 0x910 table indicates an item that can be equipped by a Ninja, Karateka or Monk (the three classes that can use the Headband).

(Alternately, consider the Onion armors: 0xB9 (1011 1001) --> 0x39 (0011 1001) --> The "OnionKid only" value)

I'm pretty sure 0x3F is the last entry in the table. Higher indices will work, but they're reading other data from after the table's end and interpreting it as class combinations, so things might break unexpectedly if that other data changes.

10
Newcomer's Board / Re: Changing class equipment selection in FF3j
« on: March 14, 2012, 03:55:28 am »
I think the "who-can-equip" byte is an index into a data table located at 0x910 in the ROM. The table's entries are three bytes long, consisting of bit flags:

First byte (0x20 ... 0x01): Ninja, Sage, Summoner, Shaman, Warlock, Bard
Second byte (0x80 ... 0x01): Conjurer, M.Knight, Karateka, Viking, Dragoon, Geomancer, Scholar, Thief
Third byte (0x80 ... 0x01): Knight, Hunter, Red Wiz, BlackWiz, WhiteWiz, Monk, Fighter, OnionKid

Armor (and possibly other items, I didn't check) have the 0x80 bit set in the "who-can-equip" byte; disregard it when indexing.

The practical upshot (spoilered for size):
Spoiler:
Code: [Select]
                                    Ni - Ninja        Sa - Sage
Su - Summoner     Sh - Shaman       Wa - Warlock      Ba - Bard
Co - Conjurer     Mk - M.Knight     Ka - Karateka     Vi - Viking
Dr - Dragoon      Ge - Geomancer    Sc - Scholar      Th - Thief
Kn - Knight       Hu - Hunter       Rw - Red Wiz      Bw - BlackWiz
Ww - WhiteWiz     Mo - Monk         Fi - Fighter      On - OnionKid

Asterisks indicate unique lines.

     |-------------------|-------------------------|-------------------------|
     | 20 10  8  4  2  1 | 80 40 20 10  8  4  2  1 | 80 40 20 10  8  4  2  1 |
     | Ni Sa Su Sh Wa Ba | Co Mk Ka Vi Dr Ge Sc Th | Kn Hu Rw Bw Ww Mo Fi On |
|----|-------------------|-------------------------|-------------------------|
| 00 | Ni Sa Su Sh Wa Ba | Co Mk Ka Vi Dr Ge Sc Th | Kn Hu Rw Bw Ww Mo Fi On | *
| 01 |                   |                         |                         | *
| 02 | Ni                |                         |                Mo       | *
| 03 | Ni Sa Su    Wa    | Co                      |       Rw Bw             | Same as 28
| 04 | Ni Sa Su    Wa    |                         |                         | *
| 05 | Ni Sa    Sh       |                         |       Rw    Ww          | *
| 06 | Ni Sa    Sh       |                         |                         | *
| 07 | Ni                |             Dr          |                         | *
| 08 | Ni                |             Dr       Th |                      On | *
| 09 | Ni                |                      Th |                         | *
| 0A | Ni                |          Vi             |                         | *
| 0B | Ni                |                         | Kn                Fi On | *
| 0C | Ni                |                         |       Rw                | *
| 0D | Ni                |                         | Kn                Fi    | *
| 0E | Ni                |                         | Kn                      | *
| 0F | Ni                |    Mk                   |                         | *
| 10 | Ni Sa Su Sh Wa    | Co                      |       Rw Bw Ww          | Same as 24
| 11 | Ni             Ba |                         |                         | *
| 12 | Ni Sa    Sh Wa    |                   Sc    |          Bw Ww          | *
| 13 | Ni                |                Ge       |                         | *
| 14 | Ni                |                         |    Hu Rw Bw       Fi On | *
| 15 | Ni                |                         |    Hu                   | *
| 16 | Ni                |          Vi Dr       Th | Kn    Rw          Fi On | Same as 1C
| 17 | Ni                |          Vi Dr    Sc Th | Kn    Rw          Fi On | *
| 18 | Ni                |          Vi Dr          | Kn    Rw          Fi On | Same as 3C
| 19 | Ni                |          Vi Dr          | Kn                Fi On | *
| 1A | Ni    Su          | Co       Vi Dr       Th | Kn                Fi    | *
| 1B | Ni Sa Su Sh Wa Ba | Co    Ka Vi Dr Ge Sc Th | Kn Hu Rw Bw Ww Mo Fi On | Same as 20
| 1C | Ni                |          Vi Dr       Th | Kn    Rw          Fi On | Same as 16
| 1D | Ni                |          Vi Dr       Th | Kn Hu Rw          Fi    | *
| 1E | Ni                |       Ka                |                Mo       | *
| 1F | Ni Sa Su Sh Wa Ba | Co             Ge Sc    |    Hu                   | *
| 20 | Ni Sa Su Sh Wa Ba | Co    Ka Vi Dr Ge Sc Th | Kn Hu Rw Bw Ww Mo Fi On | Same as 1B
| 21 | Ni             Ba |          Vi Dr       Th | Kn Hu Rw          Fi On | *
| 22 | Ni                |          Vi Dr       Th | Kn Hu Rw          Fi On | *
| 23 | Ni                |          Vi Dr          | Kn Hu             Fi    | *
| 24 | Ni Sa Su Sh Wa    | Co                      |       Rw Bw Ww          | Same as 10
| 25 | Ni Sa Su Sh Wa    | Co             Ge Sc    |       Rw Bw Ww          | *
| 26 | Ni                |          Vi Dr       Th | Kn Hu             Fi    | *
| 27 | Ni Sa Su Sh       | Co                      |       Rw    Ww          | *
| 28 | Ni Sa Su    Wa    | Co                      |       Rw Bw             | Same as 03
| 29 | Ni Sa Su Sh Wa Ba | Co       Vi Dr Ge Sc Th | Kn Hu Rw Bw Ww Mo Fi    | *
| 2A | Ni             Ba |       Ka Vi Dr Ge Sc Th | Kn Hu Rw       Mo Fi    | *
| 2B | Ni                |    Mk                   | Kn                      | *
| 2C | Ni                |          Vi Dr          | Kn Hu Rw          Fi On | *
| 2D |    Sa Su Sh Wa    | Co                      |                         | *
| 2E |    Sa       Wa    |                         |          Bw             | *
| 2F |    Sa       Wa    |                         |       Rw Bw             | *
| 30 |    Sa    Sh       |                         |             Ww          | Same as 33
| 31 |    Sa    Sh       |                         |       Rw    Ww          | *
| 32 |    Sa    Sh       |    Mk                   |    Hu Rw    Ww          | *
| 33 |    Sa    Sh       |                         |             Ww          | Same as 30
| 34 | Ni                |                      Th | Kn    Rw Bw       Fi On | *
| 35 | Ni Sa             |                   Sc    |                         | *
| 36 | Ni                |          Vi Dr          | Kn                      | Same as 37
| 37 | Ni                |          Vi Dr          | Kn                      | Same as 36
| 38 | Ni                |       Ka                |                         | *
| 39 |                   |                         |                      On | *
| 3A | Ni                |                         |                         | *
| 3B | Ni                |    Mk    Vi Dr          | Kn    Rw          Fi On | *
| 3C | Ni                |          Vi Dr          | Kn    Rw          Fi On | Same as 18
| 3D |    Sa       Wa    |                         |                         | *
| 3E |    Sa    Sh       |                         |                         | *
| 3F |    Sa Su          | Co                      |                         | *
|----|-------------------|-------------------------|-------------------------|

Pages: [1]