├── .gitignore
├── COPYING.txt
├── README.md
├── after-tools
├── bin
│ └── .gitignore
├── pom.xml
└── src
│ └── main
│ └── java
│ └── after
│ └── tools
│ ├── CheckMain.java
│ ├── Main.java
│ ├── map
│ ├── MapSurgery.java
│ └── MapSurgeryMain.java
│ └── net
│ ├── NetWorkbenchMain.java
│ └── web
│ ├── WebAdapterBaseWebClientRunnable.java
│ └── WebAdapterMain.java
├── after
├── bin
│ └── .gitignore
├── eden.dmb
├── eden.ext.dmb
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ ├── aedium
│ │ ├── DummyAnnotationToShutupIDEVerification.java
│ │ ├── ElementMarkers.java
│ │ ├── NonNull.java
│ │ ├── Nullable.java
│ │ └── package-info.java
│ │ └── after
│ │ ├── DMBMapping.java
│ │ ├── Eden.java
│ │ ├── algorithms
│ │ ├── CYCSUB.java
│ │ ├── NQCRC.java
│ │ ├── RUNSUB.java
│ │ ├── RUNSUBFailedException.java
│ │ ├── XORJump9.java
│ │ └── package-info.java
│ │ ├── annotations
│ │ ├── CacheFileID.java
│ │ ├── ClassID.java
│ │ ├── DMBList.java
│ │ ├── HasNoMeaning.java
│ │ ├── InstanceID.java
│ │ ├── ListID.java
│ │ ├── ListOfProcID.java
│ │ ├── ListOfVarID.java
│ │ ├── MobTypeID.java
│ │ ├── ProcID.java
│ │ ├── StringID.java
│ │ ├── VarID.java
│ │ └── package-info.java
│ │ ├── dm
│ │ └── package-info.java
│ │ ├── io
│ │ ├── CacheID.java
│ │ ├── DMB.java
│ │ ├── DMBCacheFileTable.java
│ │ ├── DMBClassTable.java
│ │ ├── DMBEntryBasedSubblock.java
│ │ ├── DMBGrid.java
│ │ ├── DMBHeader.java
│ │ ├── DMBInstanceTable.java
│ │ ├── DMBListTable.java
│ │ ├── DMBMapAdditionalData.java
│ │ ├── DMBMobTypeTable.java
│ │ ├── DMBObjectEntryBasedSubblock.java
│ │ ├── DMBProcTable.java
│ │ ├── DMBStandardObjectIDs.java
│ │ ├── DMBStringTable.java
│ │ ├── DMBSubblock7.java
│ │ ├── DMBValue.java
│ │ ├── DMBVarTable.java
│ │ ├── RADEntry.java
│ │ ├── RSCEntryContent.java
│ │ └── framework
│ │ │ ├── DMBContext.java
│ │ │ ├── DMBReadContext.java
│ │ │ ├── DMBWriteContext.java
│ │ │ ├── ReadContext.java
│ │ │ └── WriteContext.java
│ │ └── net
│ │ └── PacketFrame.java
│ └── test
│ └── java
│ └── after
│ └── tests
│ ├── AlgorithmTest.java
│ ├── EdenTest.java
│ └── EquivalenceTest.java
├── algorithms
├── CYCSUB.md
├── NQCRC.md
├── RUNSUB.md
└── XORJUMP9.md
├── formats
├── DMB.Bytecode.md
├── DMB.TypeValue.md
├── DMB.md
├── DMI.md
├── RAD.md
├── RSC.md
└── SAV.md
├── pom.xml
├── protocol
├── Protocol.md
└── WebClient.md
├── research-projects
├── Makefile
├── a-very-long-output.dme
├── a.dms
├── b.dms
├── chessboard.dm
├── chessboard.dmm
├── mapsurgery-game.dme
├── mapsurgery-map.dme
├── mapsurgery-map.dmm
├── modified-types.dme
├── proc-inherit-quirks-1.dme
├── proc-inherit-quirks-2.dme
├── proc-inherit-quirks-3.dme
├── rand-inherit.dme
├── savefile.dme
├── turf-atom-equivalence.dme
├── world-channel.dme
└── world-oddprops.dme
└── scripts
├── brute_force_xor_jump_9.lua
├── cycsub_r.lua
├── general_xor.lua
├── nqcrc.lua
├── rad_analyzer.lua
├── runsub_r.lua
├── runsub_t.lua
└── savefile_analyzer.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jar
2 | *.class
3 | *.zip
4 | *.iml
5 | .idea
6 | .classpath
7 | .project
8 | .settings
9 | .gradle
10 |
11 | after/build
12 | after/target
13 | after-tools/build
14 | after-tools/target
15 | after/external/test-subjects
16 |
17 | # these contain BYOND stdlib stuff
18 | research-projects/*.dmb
19 | research-projects/*.lk
20 | research-projects/*.int
21 | research-projects/*.rsc
22 | # clutters the repo
23 | research-projects/*.sav
24 | # not sure what to do with these, and it's not like they're hard to get
25 | protocol/*.dump.txt
26 |
27 | scripts/tmp.*
28 |
--------------------------------------------------------------------------------
/COPYING.txt:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unofficial BYOND Data Documentation
2 |
3 | This is incomplete documentation regarding BYOND data.
4 |
5 | There's a document which is supposed to contain protocol information but research hasn't gone far enough for it to be really worth anything.
6 |
7 | ## License
8 |
9 | byond-data-docs - documentation on BYOND formats
10 | Written starting in 2020 by 20kdc (see README.md for additional credits)
11 | To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
12 | You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see .
13 |
14 | Note that said copy of the CC0 is in 'COPYING.txt'.
15 |
16 | ## Credits
17 |
18 | 20kdc: This documentation, general structure research
19 |
20 | extools (SpaceManiac, MCHSL, 1fbff5f83b23d39d38b1dfcb4cac8d9b et al.): DM bytecode information
21 |
22 | Notes on this regard: This doesn't contain extools code.
23 | That said, if extools wants me to add their license here, ask via an issue or something.
24 |
25 | Tomeno: important parts of string decryption and protocol decryption
26 |
27 | BobOfDoom, NGGJamie, Crispy: Documented the BYOND Topic protocol at https://github.com/NGGJamie/byond-topics
28 | This was useful to understand BYOND packet framing, not covering encryption
29 |
30 | Willox: Documented some of the previously unknown properties at https://github.com/willox/dmasm/blob/dmb/src/dmb/loader.rs
31 |
32 | Stephen001: Partial documentation of some format version (right now unknown but >= 307 < 494) at https://github.com/Stephen001/Lego
33 |
34 | The Red Book (Super Saiyan X, LordAndrew, Kaiochao, DarkCampainger et al.): Informed me of the existence of modified types.
35 |
36 | ZeWaka: Informed me of the existence of Lummox JR's information on the DMI format (at http://www.byond.com/forum/post/189170#comment1077677 )
37 |
--------------------------------------------------------------------------------
/after-tools/bin/.gitignore:
--------------------------------------------------------------------------------
1 | /main/
2 | /test/
3 |
--------------------------------------------------------------------------------
/after-tools/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | t20kdc.bda
8 | after-tools
9 | 0.666-SNAPSHOT
10 |
11 | after-tools
12 | Tools for manipulating BYOND data files. DO NOT SYSTEM-PACKAGE - ALWAYS INCLUDE IN APPLICATION JAR.
13 |
14 |
15 | t20kdc.bda
16 | bda-parent-project
17 | 0.666-SNAPSHOT
18 | ..
19 |
20 |
21 |
22 |
23 |
24 | org.apache.maven.plugins
25 | maven-assembly-plugin
26 |
27 |
28 |
29 | after.tools.Main
30 |
31 |
32 |
33 |
34 |
35 | dagoodz
36 | package
37 |
38 | single
39 |
40 |
41 |
42 | jar-with-dependencies
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | t20kdc.bda
54 | after
55 | 0.666-SNAPSHOT
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/CheckMain.java:
--------------------------------------------------------------------------------
1 | package after.tools;
2 |
3 | import after.io.DMB;
4 | import after.tools.map.MapSurgeryMain;
5 |
6 | /**
7 | * Testing utility to ensure stuff can be read.
8 | */
9 | public class CheckMain {
10 | public static void main(String[] args) throws Exception {
11 | if (args.length != 1) {
12 | System.out.println("check help");
13 | return;
14 | }
15 | // -- Read --
16 | DMB dmbG = MapSurgeryMain.get(args[0]);
17 | System.out.println("ok");
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/Main.java:
--------------------------------------------------------------------------------
1 | package after.tools;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.InputStreamReader;
5 | import java.util.Random;
6 |
7 | import after.tools.map.MapSurgeryMain;
8 | import after.tools.net.NetWorkbenchMain;
9 | import after.tools.net.web.WebAdapterMain;
10 |
11 | /**
12 | * Entrypoint for easier access to sub-tools.
13 | */
14 | public class Main {
15 | private static Tool[] tools = new Tool[] {
16 | new Tool("mapsurgery", " GAME-DMB MAP-DMB OUTPUT", "Transplants a map from one DMB into another.", MapSurgeryMain.class),
17 | new Tool("proxy", "", "Launches a BYOND protocol proxy on localhost:5100 which proxies to localhost:5101. Decrypts messages.", NetWorkbenchMain.class),
18 | new Tool("broken-webadapter", " PORT", "DOESN'T WORK PROPERLY, attempts to get the webclient to do something", WebAdapterMain.class),
19 | new Tool("check", " DMB", "Checks that reading a DMB file works.", CheckMain.class),
20 | new Tool("exit", "", "Exits.", QuitMain.class)
21 | };
22 |
23 | public static void main(String[] args) throws Exception {
24 | if (args.length == 0) {
25 | String[] slogan = new String[] {
26 | "Tools for reverse-engineering to rebuild after the end of the BYOND.",
27 | "Community Edition.",
28 | "Java client when? (when you code it)",
29 | "Decompiler when? (when you code it)",
30 | "Have you tried 'proxy'?",
31 | "Have you tried 'mapsurgery'?",
32 | "Home Edition.\n" +
33 | "+---------------------------------------------+\n" +
34 | "| Activate AFTER |\n" +
35 | "+---------------------------------------------+\n" +
36 | "| AFTER: Home Edition has not been activated. |\n" +
37 | "| Click here to activate AFTER: Home Edition. |\n" +
38 | "+---------------------------------------------+",
39 | "Activating WinRAR supports a dangerously monopolistic company known for dodgy license agreements.\n" +
40 | "...that's right, I'm talking about RARLAB. (Seriously, have you looked at the unrar license? No, not the GPL'd early versions.)"
41 | };
42 | System.out.println("AFTER: " + slogan[new Random().nextInt(slogan.length)]);
43 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
44 | while (true) {
45 | System.out.append("> ");
46 | System.out.flush();
47 | try {
48 | String[] words = br.readLine().split(" ");
49 | if (words.length != 0)
50 | main(words);
51 | } catch (Exception e2) {
52 | e2.printStackTrace();
53 | }
54 | }
55 | }
56 | for (Tool t : tools) {
57 | if (args[0].equals(t.name)) {
58 | String[] remainingArgs = new String[args.length - 1];
59 | System.arraycopy(args, 1, remainingArgs, 0, remainingArgs.length);
60 | t.main.getMethod("main", String[].class).invoke(null, new Object[] {remainingArgs});
61 | return;
62 | }
63 | }
64 | help();
65 | }
66 |
67 | private static void help() {
68 | System.out.println("java -jar after-tools.jar SUBCOMMAND ARGS...");
69 | for (Tool t : tools) {
70 | System.out.println(t.name + t.args);
71 | System.out.println(" " + t.description);
72 | }
73 | }
74 |
75 | private static class Tool {
76 | public final String name, args, description;
77 | public final Class main;
78 | public Tool(String n, String a, String d, Class m) {
79 | name = n;
80 | args = a;
81 | description = d;
82 | main = m;
83 | }
84 | }
85 |
86 | private static class QuitMain {
87 | public static void main(String[] args) {
88 | System.exit(0);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/map/MapSurgery.java:
--------------------------------------------------------------------------------
1 | package after.tools.map;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 |
7 | import after.DMBMapping;
8 | import after.io.DMB;
9 | import after.io.DMBEntryBasedSubblock;
10 | import after.io.DMBInstanceTable;
11 | import after.io.DMBMapAdditionalData;
12 | import after.io.DMBValue;
13 |
14 | public class MapSurgery {
15 | /**
16 | * Imports a map from another DMB file, deleting the existing grid.
17 | * @param dmbG the place to inject the map
18 | * @param dmbM the map to inject
19 | */
20 | public static void run(DMB dmbG, DMB dmbM) {
21 | // reset grid (though don't fill in arrays yet)
22 | int total = dmbM.grid.turf.length;
23 | dmbG.grid.turf = new int[total];
24 | dmbG.grid.area = new int[total];
25 | dmbG.grid.additionalTurfs = new int[total];
26 | dmbG.grid.xSize = dmbM.grid.xSize;
27 | dmbG.grid.ySize = dmbM.grid.ySize;
28 | dmbG.grid.zSize = dmbM.grid.zSize;
29 | // prepare fallback instance
30 | int fallbackTurfInstance = getOrMakeTurfInstance(dmbG);
31 | DMBMapping mapping = new DMBMapping(dmbM, dmbG);
32 | HashMap, Integer> lists = new HashMap, Integer>();
33 | // fill in grids
34 | for (int i = 0; i < total; i++) {
35 | // set sane defaults
36 | dmbG.grid.turf[i] = fallbackTurfInstance;
37 | dmbG.grid.area[i] = DMBEntryBasedSubblock.OBJ_NULL;
38 | dmbG.grid.additionalTurfs[i] = DMBEntryBasedSubblock.OBJ_NULL;
39 | // apply stuff
40 | Integer cTurf = mapping.mapValue(DMBValue.INSTANCE_TYPEPATH, dmbM.grid.turf[i]);
41 | if (cTurf != null) {
42 | dmbG.grid.turf[i] = cTurf;
43 | } else {
44 | System.out.println("unable to map turf @ " + i);
45 | }
46 | Integer cArea = mapping.mapValue(DMBValue.INSTANCE_TYPEPATH, dmbM.grid.area[i]);
47 | if (cArea != null) {
48 | dmbG.grid.area[i] = cArea;
49 | } else {
50 | System.out.println("unable to map area @ " + i);
51 | }
52 | if (dmbM.grid.additionalTurfs[i] != DMBEntryBasedSubblock.OBJ_NULL) {
53 | int[] addTurfsO = dmbM.lists.entries.get(dmbM.grid.additionalTurfs[i]);
54 | ArrayList addTurfsN = new ArrayList<>(addTurfsO.length);
55 | for (int x = 0; x < addTurfsO.length; x++) {
56 | Integer v = mapping.mapValue(DMBValue.TURF_TYPEPATH, addTurfsO[x]);
57 | if (v != null)
58 | addTurfsN.add(v);
59 | }
60 | Integer existingList = lists.get(addTurfsN);
61 | if (existingList == null) {
62 | int[] addTurfsNA = new int[addTurfsN.size()];
63 | for (int x = 0; x < addTurfsNA.length; x++)
64 | addTurfsNA[x] = addTurfsN.get(x);
65 | int res = dmbM.lists.add(addTurfsNA);
66 | lists.put(addTurfsN, res);
67 | existingList = res;
68 | }
69 | dmbG.grid.additionalTurfs[i] = existingList;
70 | }
71 | }
72 | dmbG.mapAdditionalData.entries.clear();
73 | for (DMBMapAdditionalData.Entry ent : dmbM.mapAdditionalData.entries) {
74 | DMBMapAdditionalData.Entry oh = new DMBMapAdditionalData.Entry();
75 | oh.offset = ent.offset;
76 | Integer mapped = mapping.mapValue(DMBValue.INSTANCE_TYPEPATH, oh.instance);
77 | if (mapped != null)
78 | oh.instance = mapped;
79 | dmbG.mapAdditionalData.entries.add(ent);
80 | }
81 | dmbG.fixStrings();
82 | }
83 |
84 | private static int getOrMakeTurfInstance(DMB dmbG) {
85 | int target = dmbG.standardObjectIDs.turf;
86 | for (int i = 0, iC = dmbG.instances.entries.size(); i < iC; i++) {
87 | DMBInstanceTable.Entry ent = dmbG.instances.entries.get(i);
88 | if ((ent.value.type == DMBValue.TURF_TYPEPATH) && (ent.value.value == target) && (ent.initializer == DMBEntryBasedSubblock.OBJ_NULL))
89 | return i;
90 | }
91 | DMBInstanceTable.Entry ent = new DMBInstanceTable.Entry();
92 | ent.value = new DMBValue(DMBValue.TURF_TYPEPATH, target);
93 | ent.initializer = DMBEntryBasedSubblock.OBJ_NULL;
94 | return dmbG.instances.add(ent);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/map/MapSurgeryMain.java:
--------------------------------------------------------------------------------
1 | package after.tools.map;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 |
8 | import after.io.DMB;
9 | import after.io.framework.DMBReadContext;
10 | import after.io.framework.DMBWriteContext;
11 |
12 | public class MapSurgeryMain {
13 | public static void main(String[] args) throws Exception {
14 | if (args.length != 3) {
15 | System.out.println("check help");
16 | return;
17 | }
18 | // -- Read --
19 | DMB dmbG = get(args[0]);
20 | DMB dmbM = get(args[1]);
21 | // -- Map transplantation into dmbG --
22 | MapSurgery.run(dmbG, dmbM);
23 | // -- Write --
24 | FileOutputStream fos = new FileOutputStream(args[2]);
25 | dmbG.write(new DMBWriteContext(false) {
26 | @Override
27 | protected void i8Internal(int value) {
28 | try {
29 | fos.write(value);
30 | } catch (IOException e) {
31 | throw new RuntimeException(e);
32 | }
33 | }
34 | });
35 | fos.close();
36 | }
37 |
38 | public static DMB get(String args) throws Exception {
39 | byte[] gameDMBData = Files.readAllBytes(new File(args).toPath());
40 | DMB d = new DMB();
41 | d.read(new DMBReadContext(gameDMBData, true));
42 | return d;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/net/NetWorkbenchMain.java:
--------------------------------------------------------------------------------
1 | package after.tools.net;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.net.InetSocketAddress;
6 | import java.net.ServerSocket;
7 | import java.net.Socket;
8 | import java.nio.ByteOrder;
9 | import java.nio.channels.SelectionKey;
10 | import java.nio.channels.Selector;
11 | import java.nio.channels.ServerSocketChannel;
12 | import java.nio.channels.SocketChannel;
13 | import java.util.Set;
14 |
15 | import after.algorithms.RUNSUB;
16 | import after.net.PacketFrame;
17 |
18 | /**
19 | * Testing utility for studying the protocol.
20 | */
21 | public class NetWorkbenchMain {
22 | public static void main(String[] args) throws Exception {
23 | System.out.println("Listening on port 5100. Expects target server on localhost port 5101.");
24 | ServerSocketChannel ss = ServerSocketChannel.open();
25 | ss.bind(new InetSocketAddress(5100));
26 |
27 | SocketChannel sockC = ss.accept();
28 | SocketChannel sockS = SocketChannel.open(new InetSocketAddress("localhost", 5101));
29 |
30 | Selector main = Selector.open();
31 |
32 | DataInputStream disC = new DataInputStream(sockC.socket().getInputStream());
33 | DataInputStream disS = new DataInputStream(sockS.socket().getInputStream());
34 |
35 | PacketFrame pfC = new PacketFrame();
36 | PacketFrame pfS = new PacketFrame();
37 |
38 | DataOutputStream dosC = new DataOutputStream(sockC.socket().getOutputStream());
39 | DataOutputStream dosS = new DataOutputStream(sockS.socket().getOutputStream());
40 |
41 | // various states
42 | int NEST_NORMAL = 0;
43 | int NEST_CTOS_HANDSHAKE = 1;
44 | int NEST_STOC_HANDSHAKE = 2;
45 | int state = NEST_CTOS_HANDSHAKE;
46 |
47 | int encryptionKey = 0;
48 |
49 | while (true) {
50 | // blocking off...
51 | sockC.configureBlocking(false);
52 | sockS.configureBlocking(false);
53 |
54 | SelectionKey keyC = sockC.register(main, SelectionKey.OP_READ);
55 | SelectionKey keyS = sockS.register(main, SelectionKey.OP_READ);
56 |
57 | main.select();
58 | Set active = main.selectedKeys();
59 |
60 | boolean hitC = active.contains(keyC);
61 | boolean hitS = active.contains(keyS);
62 |
63 | keyC.cancel();
64 | keyS.cancel();
65 |
66 | main.selectNow();
67 |
68 | sockC.configureBlocking(true);
69 | sockS.configureBlocking(true);
70 |
71 | // ...back to blocking for simpler logic (this is just a workbench)
72 |
73 | if (hitC) {
74 | pfC.read(disC);
75 | pfC.write(dosS);
76 | logAndDecrypt("C", pfC, encryptionKey);
77 | if (state == NEST_CTOS_HANDSHAKE && pfC.type == 1) {
78 | // initial handshake
79 | pfC.hasSeq = true;
80 | state = NEST_STOC_HANDSHAKE;
81 | encryptionKey = pfC.data.getInt(8);
82 | encryptionKey += pfC.data.getInt(0) + (pfC.data.getInt(4) * 0x10000);
83 | } else if (state != NEST_NORMAL) {
84 | System.out.println("*** ctos " + pfC.type + " is odd");
85 | }
86 | }
87 | if (hitS) {
88 | pfS.read(disS);
89 | pfS.write(dosC);
90 | logAndDecrypt("S", pfS, encryptionKey);
91 | if (state == NEST_STOC_HANDSHAKE && pfS.type == 1) {
92 | // skip past padding
93 | int ptr = 11;
94 | while (true) {
95 | int v = pfS.data.getInt(ptr);
96 | ptr += 4;
97 | v += 0x71bd632f;
98 | v &= 0x04008000;
99 | if (v == 0)
100 | break;
101 | }
102 | encryptionKey += pfS.data.getInt(ptr);
103 | // right, done
104 | state = NEST_NORMAL;
105 | } else if (state != NEST_NORMAL) {
106 | System.out.println("*** stoc " + pfS.type + " is odd");
107 | }
108 | }
109 | }
110 | }
111 |
112 | private static void logAndDecrypt(String where, PacketFrame pf, int key) {
113 | System.out.println(where + " " + Integer.toHexString(pf.sequence & 0xFFFF) + " " + Integer.toHexString(pf.type & 0xFFFF));
114 | int len = pf.data.position();
115 | for (int pass = 0; pass < 2; pass++) {
116 | for (int i = 0; i < len; i++) {
117 | String hx = Integer.toHexString(pf.dataArray[i] & 0xFF);
118 | if (hx.length() == 1)
119 | hx = "0" + hx;
120 | System.out.print(" " + hx);
121 | }
122 | System.out.println();
123 | if (pass == 0 && key != 0) {
124 | System.out.println("decrypted");
125 | RUNSUB.decrypt(pf.dataArray, 0, len, key);
126 | len--;
127 | } else {
128 | // final pass: ASCII
129 | for (int i = 0; i < len; i++) {
130 | char c = (char) (pf.dataArray[i] & 0xFF);
131 | if (c >= 32 && c <= 0x7E) {
132 | System.out.print(" " + c + " ");
133 | } else {
134 | System.out.print(" . ");
135 | }
136 | }
137 | System.out.println();
138 | break;
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/net/web/WebAdapterBaseWebClientRunnable.java:
--------------------------------------------------------------------------------
1 | package after.tools.net.web;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataInputStream;
5 | import java.io.DataOutputStream;
6 | import java.net.Socket;
7 | import java.nio.charset.StandardCharsets;
8 | import java.nio.file.Files;
9 | import java.nio.file.Paths;
10 | import java.security.MessageDigest;
11 | import java.util.Base64;
12 | import java.util.LinkedList;
13 |
14 | /**
15 | * Internals.
16 | */
17 | abstract class WebAdapterBaseWebClientRunnable implements Runnable {
18 | public final Socket clientSocket;
19 | public DataInputStream clientInput;
20 | public DataOutputStream clientOutput;
21 | public WebAdapterBaseWebClientRunnable(Socket s) {
22 | clientSocket = s;
23 | }
24 |
25 | @Override
26 | public void run() {
27 | try {
28 | clientInput = new DataInputStream(clientSocket.getInputStream());
29 | clientOutput = new DataOutputStream(clientSocket.getOutputStream());
30 |
31 | // Initialization, Part 1 (request parsing)
32 | LinkedList headerLines = new LinkedList<>();
33 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
34 | while (true) {
35 | byte b = clientInput.readByte();
36 | if (b == '\r')
37 | continue;
38 | if (b == '\n') {
39 | String line = new String(baos.toByteArray(), StandardCharsets.UTF_8);
40 | baos.reset();
41 | if (line.equals(""))
42 | break;
43 | headerLines.add(line);
44 | } else {
45 | baos.write(b);
46 | }
47 | }
48 |
49 | // Initialization, Part 2 (do we want to give the browser a file?)
50 | String path = headerLines.getFirst().split(" ")[1];
51 |
52 | boolean isWebSocket = false;
53 | String webSocketKey = null;
54 |
55 | for (int i = 0; i < headerLines.size(); i++) {
56 | String[] kv = headerLines.get(i).split(":");
57 | if (kv[0].trim().equalsIgnoreCase("Upgrade"))
58 | isWebSocket = true;
59 | else if (kv[0].trim().equalsIgnoreCase("Sec-WebSocket-Key")) {
60 | // just to be sure
61 | baos.reset();
62 | baos.write(kv[1].trim().getBytes(StandardCharsets.UTF_8));
63 | baos.write(("258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes(StandardCharsets.UTF_8));
64 | // "seemingly" overcomplicated? SEEMINGLY?
65 | // No. NO. THIS IS UTTER INSANITY.
66 | // Dear goodness what is WRONG with whoever designed this
67 | webSocketKey = Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA1").digest(baos.toByteArray()));
68 | }
69 | }
70 |
71 | System.out.println(path + " " + isWebSocket + " " + webSocketKey);
72 |
73 | if (isWebSocket) {
74 | clientOutput.writeBytes("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n");
75 | if (webSocketKey != null)
76 | clientOutput.writeBytes("Sec-WebSocket-Accept: " + webSocketKey + "\r\n");
77 | clientOutput.writeBytes("Sec-WebSocket-Version: 13\r\n\r\n");
78 | handleWebSocket();
79 | } else {
80 | String status = "200 OK";
81 | byte[] contentResult;
82 | if (path.contains("\\")) {
83 | status = "404 Not Found";
84 | contentResult = "NO".getBytes();
85 | } else {
86 | String[] bits = path.split("/");
87 | if (bits.length == 0 || bits[bits.length - 1].equals("play")) {
88 | StringBuilder ongoing = new StringBuilder();
89 | // this is actually all that needs to be done
90 | ongoing.append("");
91 | ongoing.append("");
92 | ongoing.append("");
93 | contentResult = ongoing.toString().getBytes();
94 | } else {
95 | try {
96 | contentResult = Files.readAllBytes(Paths.get(bits[bits.length - 1]));
97 | } catch (Exception e2) {
98 | status = "404 Not Found";
99 | contentResult = e2.getMessage().getBytes();
100 | }
101 | }
102 | }
103 | clientOutput.writeBytes("HTTP/1.1 " + status + "\r\nConnection: close\r\n");
104 | clientOutput.writeBytes("Content-Length: " + contentResult.length + "\r\n\r\n");
105 | clientOutput.write(contentResult);
106 | clientOutput.flush();
107 | }
108 |
109 | } catch (Exception e) {
110 | e.printStackTrace();
111 | } finally {
112 | try {
113 | clientSocket.close();
114 | } catch (Exception e2) {
115 | // DROPPING A RESOURCE MUST NEVER BE ALLOWED TO FAIL.
116 | }
117 | }
118 | }
119 |
120 | public abstract void handleWebSocket() throws Exception;
121 | }
122 |
--------------------------------------------------------------------------------
/after-tools/src/main/java/after/tools/net/web/WebAdapterMain.java:
--------------------------------------------------------------------------------
1 | package after.tools.net.web;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataInputStream;
5 | import java.io.DataOutputStream;
6 | import java.io.File;
7 | import java.net.ServerSocket;
8 | import java.net.Socket;
9 | import java.nio.charset.StandardCharsets;
10 | import java.nio.file.Files;
11 | import java.nio.file.Paths;
12 | import java.security.MessageDigest;
13 | import java.util.Base64;
14 | import java.util.LinkedList;
15 |
16 | import after.net.PacketFrame;
17 |
18 | /**
19 | * Ok, so, APPARENTLY, the WebClient protocol has been made different enough in ways that make writing my own adapter...
20 | * not worth the effort, so the NEW purpose of this is as a workbench for webclient stuff
21 | * might make it an adapter again in future
22 | */
23 | public class WebAdapterMain {
24 |
25 | public static void main(String[] args) throws Exception {
26 | LinkedList spoonfeedWinset = new LinkedList<>();
27 | for (String s : new File(".").list())
28 | if (s.endsWith(".dms") && !s.equalsIgnoreCase("defaultSkin.dms"))
29 | spoonfeedWinset.add(Files.readAllBytes(Paths.get(s)));
30 | spoonfeedWinset.add(Files.readAllBytes(Paths.get("defaultSkin.dms")));
31 |
32 | int sourcePort = Integer.parseInt(args[0]);
33 |
34 | ServerSocket ss = new ServerSocket(sourcePort);
35 |
36 | while (true) {
37 | Socket clientSocket = ss.accept();
38 | new Thread(new WebAdapterBaseWebClientRunnable(clientSocket) {
39 |
40 | @Override
41 | public void handleWebSocket() throws Exception {
42 | PacketFrame webFrame = new PacketFrame();
43 |
44 | // Say hello to the client
45 | webFrame.begin(0x0001);
46 | webFrame.data.putInt(0x200);
47 | webFrame.data.putInt(0x200);
48 | webFrame.writeWebSocket(clientOutput);
49 |
50 | // Winset
51 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
52 | baos.write(0);
53 | baos.write(0);
54 | for (byte[] sp : spoonfeedWinset) {
55 | int len = sp.length;
56 | baos.write(len);
57 | baos.write(len >> 8);
58 | baos.write(sp);
59 | }
60 | PacketFrame.writeWebSocketInternal(clientOutput, (short) 0x00E5, baos.toByteArray(), 0, baos.size());
61 |
62 | // Get the client to boot
63 | webFrame.begin(0x000E);
64 | webFrame.data.putShort((short) 0);
65 | webFrame.data.putInt(0);
66 | webFrame.writeWebSocket(clientOutput);
67 |
68 | // put up a test verb
69 | webFrame.begin(0x00d2);
70 | webFrame.data.put((byte) 0);
71 | webFrame.data.putShort((short) 1);
72 | webFrame.data.putShort((short) 0);
73 | webFrame.data.put((byte) 2);
74 | for (int i = 0; i < 3; i++) {
75 | webFrame.data.putShort((short) 5);
76 | byte[] bt = "HELLO".getBytes();
77 | for (byte b : bt)
78 | webFrame.data.put(b);
79 | }
80 | webFrame.data.putShort((short) 0);
81 | webFrame.data.put((byte) 0);
82 | webFrame.writeWebSocket(clientOutput);
83 |
84 | // stop
85 | while (true) {
86 | Thread.sleep(5000);
87 | webFrame.begin(0x0027);
88 | byte[] bt = "QUACK!
\n".getBytes();
89 | for (byte b : bt)
90 | webFrame.data.put(b);
91 | webFrame.data.put((byte) 0);
92 | webFrame.writeWebSocket(clientOutput);
93 | }
94 | }
95 | }).start();
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/after/bin/.gitignore:
--------------------------------------------------------------------------------
1 | /main/
2 | /test/
3 |
--------------------------------------------------------------------------------
/after/eden.dmb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/20kdc/byond-data-docs/bf6023dfa3860312a299f2d335e251d2dece5abd/after/eden.dmb
--------------------------------------------------------------------------------
/after/eden.ext.dmb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/20kdc/byond-data-docs/bf6023dfa3860312a299f2d335e251d2dece5abd/after/eden.ext.dmb
--------------------------------------------------------------------------------
/after/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | t20kdc.bda
8 | after
9 | 0.666-SNAPSHOT
10 |
11 | after
12 | Library for reading and writing BYOND DMB files. DO NOT SYSTEM-PACKAGE - ALWAYS INCLUDE IN APPLICATION JAR.
13 |
14 |
15 | t20kdc.bda
16 | bda-parent-project
17 | 0.666-SNAPSHOT
18 | ..
19 |
20 |
21 |
22 |
23 | junit
24 | junit
25 | 4.13.1
26 | test
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/after/src/main/java/aedium/DummyAnnotationToShutupIDEVerification.java:
--------------------------------------------------------------------------------
1 | package aedium;
2 |
3 | /**
4 | * Set "NonNullByDefault" to this.
5 | */
6 | public @interface DummyAnnotationToShutupIDEVerification {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/after/src/main/java/aedium/ElementMarkers.java:
--------------------------------------------------------------------------------
1 | package aedium;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Annotation;
7 | import java.lang.annotation.Documented;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.Target;
10 |
11 | @Documented
12 | @Retention(RUNTIME)
13 | @Target(FIELD)
14 | public @interface ElementMarkers {
15 | Class[] value();
16 | }
17 |
--------------------------------------------------------------------------------
/after/src/main/java/aedium/NonNull.java:
--------------------------------------------------------------------------------
1 | package aedium;
2 |
3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
4 | import static java.lang.annotation.ElementType.FIELD;
5 | import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
6 | import static java.lang.annotation.ElementType.PARAMETER;
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | import java.lang.annotation.Documented;
11 | import java.lang.annotation.Retention;
12 | import java.lang.annotation.Target;
13 |
14 | /**
15 | * RUNTIME retention. Checked by ALFRED.
16 | * Also serves as the local equivalent to the Eclipse thingy.
17 | * DO NOT JUST BLINDLY REPLACE THIS WITH ECLIPSE JDT ANNOTATIONS.
18 | */
19 | @Documented
20 | @Retention(RUNTIME)
21 | @Target({ TYPE, FIELD, PARAMETER, LOCAL_VARIABLE, ANNOTATION_TYPE })
22 | public @interface NonNull {
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/after/src/main/java/aedium/Nullable.java:
--------------------------------------------------------------------------------
1 | package aedium;
2 |
3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
4 | import static java.lang.annotation.ElementType.FIELD;
5 | import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
6 | import static java.lang.annotation.ElementType.PARAMETER;
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | import java.lang.annotation.Documented;
11 | import java.lang.annotation.Retention;
12 | import java.lang.annotation.Target;
13 |
14 | /**
15 | * RUNTIME retention. Checked by ALFRED.
16 | * DO NOT JUST BLINDLY REPLACE THIS WITH ECLIPSE JDT ANNOTATIONS.
17 | */
18 | @Documented
19 | @Retention(RUNTIME)
20 | @Target({ TYPE, FIELD, PARAMETER, LOCAL_VARIABLE, ANNOTATION_TYPE })
21 | public @interface Nullable {
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/after/src/main/java/aedium/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Quick & dirty reimplementation of the Eclipse nullability annotations.
3 | * However, they are set to *runtime* retention for use in AFTER.
4 | */
5 | package aedium;
--------------------------------------------------------------------------------
/after/src/main/java/after/DMBMapping.java:
--------------------------------------------------------------------------------
1 | package after;
2 |
3 | import java.util.HashMap;
4 |
5 | import after.annotations.ClassID;
6 | import after.annotations.HasNoMeaning;
7 | import after.annotations.InstanceID;
8 | import after.annotations.MobTypeID;
9 | import after.io.DMB;
10 | import after.io.DMBClassTable;
11 | import after.io.DMBEntryBasedSubblock;
12 | import after.io.DMBInstanceTable;
13 | import after.io.DMBMobTypeTable;
14 | import after.io.DMBProcTable;
15 | import after.io.DMBStringTable;
16 | import after.io.DMBValue;
17 |
18 | /**
19 | * Creates a mapping between two DMB files (a source and a target).
20 | * Note that instances and strings may be automatically created if necessary.
21 | */
22 | public class DMBMapping {
23 | public final DMB dmbSrc, dmbTgt;
24 |
25 | // mapping tables
26 | // setup in constructor, never changed after
27 | private HashMap classMap = new HashMap<>();
28 | private HashMap mobTypeMap = new HashMap<>();
29 | // dynamic
30 | private HashMap stringMap = new HashMap<>();
31 | private HashMap instanceMap = new HashMap<>();
32 |
33 | public final int emptyListTgt;
34 |
35 | public DMBMapping(DMB s, DMB t) {
36 | dmbSrc = s;
37 | dmbTgt = t;
38 | int emptyListWork = -1;
39 | for (int i = 0, iC = dmbTgt.lists.entries.size(); i < iC; i++) {
40 | if (dmbTgt.lists.entries.get(i).length == 0) {
41 | emptyListWork = i;
42 | break;
43 | }
44 | }
45 | if (emptyListWork == -1)
46 | emptyListWork = dmbTgt.lists.add(new int[0]);
47 | emptyListTgt = emptyListWork;
48 | classMap.put(DMBEntryBasedSubblock.OBJ_NULL, DMBEntryBasedSubblock.OBJ_NULL);
49 | for (int i = 0, iC = dmbSrc.classes.entries.size(); i < iC; i++) {
50 | DMBClassTable.Entry iV = dmbSrc.classes.entries.get(i);
51 | if (iV.name == DMBEntryBasedSubblock.OBJ_NULL)
52 | continue;
53 | byte[] iVN = dmbSrc.strings.entries.get(iV.name);
54 | for (int j = 0, jC = dmbTgt.classes.entries.size(); j < jC; j++) {
55 | DMBClassTable.Entry jV = dmbTgt.classes.entries.get(j);
56 | byte[] jVN = dmbTgt.strings.entries.get(jV.name);
57 | if (DMBStringTable.equal(iVN, jVN)) {
58 | classMap.put(i, j);
59 | break;
60 | }
61 | }
62 | }
63 | mobTypeMap.put(DMBEntryBasedSubblock.OBJ_NULL, DMBEntryBasedSubblock.OBJ_NULL);
64 | for (int i = 0, iC = dmbSrc.mobTypes.entries.size(); i < iC; i++) {
65 | DMBMobTypeTable.Entry iV = dmbSrc.mobTypes.entries.get(i);
66 | Integer mappedClass = classMap.get(iV.clazz);
67 | if (mappedClass == null)
68 | continue;
69 | int mappedClassI = mappedClass;
70 | for (int j = 0, jC = dmbTgt.mobTypes.entries.size(); j < jC; j++) {
71 | DMBMobTypeTable.Entry jV = dmbTgt.mobTypes.entries.get(j);
72 | if (mappedClassI == jV.clazz) {
73 | mobTypeMap.put(i, j);
74 | break;
75 | }
76 | }
77 | }
78 | stringMap.put(DMBEntryBasedSubblock.OBJ_NULL, DMBEntryBasedSubblock.OBJ_NULL);
79 | instanceMap.put(DMBEntryBasedSubblock.OBJ_NULL, DMBEntryBasedSubblock.OBJ_NULL);
80 | }
81 |
82 | /**
83 | * Maps a string ID into the target.
84 | * Or creates one if necessary.
85 | */
86 | private int mapString(int i) {
87 | Integer mapped = stringMap.get(i);
88 | if (mapped != null)
89 | return mapped;
90 | byte[] iV = dmbSrc.strings.entries.get(i);
91 | for (int j = 0, jC = dmbTgt.strings.entries.size(); j < jC; j++) {
92 | byte[] jV = dmbTgt.strings.entries.get(j);
93 | if (DMBStringTable.equal(iV, jV)) {
94 | stringMap.put(i, j);
95 | break;
96 | }
97 | }
98 | int r = dmbTgt.strings.add(dmbSrc.strings.entries.get(i));
99 | stringMap.put(i, r);
100 | return r;
101 | }
102 |
103 | /**
104 | * Maps an instance ID to the target.
105 | * Or creates one if necessary.
106 | * Can return Java null if the instance requires a class/mobType that doesn't exist.
107 | */
108 | private Integer mapInstance(int i) {
109 | Integer mapped = instanceMap.get(i);
110 | if (mapped != null)
111 | return mapped;
112 | // If it has no initializer, then we do things the easy way.
113 | // If it has an initializer, then things get hard and we might as well just build a new instance.
114 | DMBInstanceTable.Entry iV = dmbSrc.instances.entries.get(i);
115 | // No matter what, we have to map this for equality checks / etc.
116 | DMBValue iVValueMapped = mapValue(iV.value);
117 | if (iVValueMapped == null)
118 | return null;
119 | // There's a possibility this is dreadfully simple. Check for that.
120 | if (iV.initializer == DMBEntryBasedSubblock.OBJ_NULL) {
121 | for (int j = 0, jC = dmbTgt.instances.entries.size(); j < jC; j++) {
122 | DMBInstanceTable.Entry jV = dmbTgt.instances.entries.get(j);
123 | if (jV.initializer == DMBEntryBasedSubblock.OBJ_NULL) {
124 | if (iVValueMapped.equals(jV.value)) {
125 | instanceMap.put(i, j);
126 | return j;
127 | }
128 | }
129 | }
130 | }
131 | // Not so simple, then.
132 | Integer proc = mapInitializerProc(iV.initializer);
133 | if (proc == null)
134 | return null;
135 | DMBInstanceTable.Entry newInst = new DMBInstanceTable.Entry();
136 | newInst.value = iVValueMapped;
137 | newInst.initializer = proc;
138 | int newInstId = dmbTgt.instances.add(newInst);
139 | instanceMap.put(i, newInstId);
140 | return newInstId;
141 | }
142 |
143 | /**
144 | * Maps an initializer proc. Note that this always creates a new proc.
145 | * @param i source proc ID
146 | * @return target proc ID on success, null on failure
147 | */
148 | private Integer mapInitializerProc(int i) {
149 | DMBProcTable.Entry iV = dmbSrc.procs.entries.get(i);
150 | int[] originalCode = dmbSrc.lists.entries.get(iV.code);
151 | int[] newCode = new int[originalCode.length];
152 | int oPC = 0;
153 | int nPC = 0;
154 | while (oPC < originalCode.length) {
155 | int op = originalCode[oPC++];
156 | newCode[nPC++] = op;
157 | if (op == 0) {
158 | // END
159 | break;
160 | } else if (op == 0x34) {
161 | // SETVAR
162 | int tmp;
163 | tmp = newCode[nPC++] = originalCode[oPC++];
164 | if (tmp != 65500) {
165 | System.out.println("incorrect initializer sv[1] " + tmp);
166 | return null;
167 | }
168 | tmp = newCode[nPC++] = originalCode[oPC++];
169 | if (tmp != 65486) {
170 | System.out.println("incorrect initializer sv[2] " + tmp);
171 | return null;
172 | }
173 | int field = originalCode[oPC++];
174 | //System.out.println("initializer dbg: " + dmbSrc.strings.getString(field) + " = ...");
175 | newCode[nPC++] = mapString(field);
176 | } else if (op == 0x50) {
177 | // PUSHI
178 | newCode[nPC++] = originalCode[oPC++];
179 | } else if (op == 0x60) {
180 | // PUSHVAL
181 | int type = newCode[nPC++] = originalCode[oPC++];
182 | if (type == DMBValue.NUMBER) {
183 | newCode[nPC++] = originalCode[oPC++];
184 | newCode[nPC++] = originalCode[oPC++];
185 | } else {
186 | int val = originalCode[oPC++];
187 | Integer res = mapValue(type, val);
188 | if (res == null) {
189 | System.out.println("initializer pushval map failure");
190 | return null;
191 | }
192 | newCode[nPC++] = res;
193 | }
194 | } else {
195 | System.out.println("bad initializer, op " + op);
196 | return null;
197 | }
198 | }
199 | if (nPC != newCode.length)
200 | System.out.println("warning: initializer code size different (bad mapping?)");
201 | DMBProcTable.Entry newProc = new DMBProcTable.Entry();
202 | newProc.args = emptyListTgt;
203 | newProc.code = dmbTgt.lists.add(newCode);
204 | newProc.locals = emptyListTgt;
205 | newProc.flags = iV.flags;
206 | return dmbTgt.procs.add(newProc);
207 | }
208 |
209 | /**
210 | * Maps a source value to a target value.
211 | * Note that this can return java null (unmappable)
212 | */
213 | public Integer mapValue(int type, int value) {
214 | switch (type) {
215 | case DMBValue.NULL:
216 | case DMBValue.NUMBER:
217 | return value;
218 | case DMBValue.STRING:
219 | return mapString(value);
220 | case DMBValue.AREA_TYPEPATH:
221 | case DMBValue.DATUM_TYPEPATH:
222 | case DMBValue.IMAGE_TYPEPATH:
223 | case DMBValue.OBJ_TYPEPATH:
224 | case DMBValue.TURF_TYPEPATH:
225 | return classMap.get(value);
226 | case DMBValue.INSTANCE_TYPEPATH:
227 | return mapInstance(value);
228 | case DMBValue.MOB_TYPEPATH:
229 | return mobTypeMap.get(value);
230 | default:
231 | System.err.println("DMBMapping cannot map type " + type);
232 | return null;
233 | }
234 | }
235 |
236 | /**
237 | * Maps a source value to a target value.
238 | * Note that this can return java null (unmappable)
239 | */
240 | public DMBValue mapValue(DMBValue dv) {
241 | Integer res = mapValue(dv.type, dv.value);
242 | if (res == null)
243 | return null;
244 | return new DMBValue(dv.type, res);
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/after/src/main/java/after/Eden.java:
--------------------------------------------------------------------------------
1 | package after;
2 |
3 | import after.io.DMB;
4 | import after.io.DMBClassTable;
5 | import after.io.DMBInstanceTable;
6 | import after.io.DMBMobTypeTable;
7 | import after.io.DMBValue;
8 |
9 | import static after.io.DMBObjectEntryBasedSubblock.OBJ_NULL;
10 |
11 | import after.annotations.MobTypeID;
12 |
13 | /**
14 | * Current state of the BYOND-less DMB project ('Eden').
15 | */
16 | public class Eden {
17 | private static int addClass(DMB dmb, String name, int parent) {
18 | DMBClassTable.Entry datum = new DMBClassTable.Entry();
19 | datum.name = dmb.strings.of(name);
20 | datum.parent = parent;
21 | return dmb.classes.add(datum);
22 | }
23 |
24 | private static DMBClassTable.Entry cls(DMB dmb, int id) {
25 | return dmb.classes.entries.get(id);
26 | }
27 |
28 | public static DMB newEden() {
29 | DMB dmb = new DMB();
30 | // I suspect this is relied upon, don't want to jinx it
31 | dmb.strings.entries.add(new byte[0]);
32 |
33 | int mob = addClass(dmb, "", OBJ_NULL);
34 | dmb.standardObjectIDs.client = mob;
35 |
36 | DMBMobTypeTable.Entry mobType = new DMBMobTypeTable.Entry();
37 | mobType.clazz = mob;
38 | dmb.mobTypes.add(mobType);
39 | dmb.standardObjectIDs.mob = 0;
40 |
41 | dmb.fixStrings();
42 | return dmb;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/after/src/main/java/after/algorithms/CYCSUB.java:
--------------------------------------------------------------------------------
1 | package after.algorithms;
2 |
3 | /**
4 | * Implementation of CYCSUB.
5 | * This implementation operates on whole buffers at a time in-place.
6 | */
7 | public class CYCSUB {
8 | /**
9 | * Decrypts data using CYCSUB.
10 | */
11 | public static void decrypt(byte[] data, int offset, int length, byte[] key, int keyOffset, int keyLength) {
12 | byte checksum = 0;
13 | int keyIndex = 0;
14 | for (int i = length - 1; i >= 0; i--) {
15 | byte roundKey = (byte) (checksum + key[keyOffset + keyIndex]);
16 | // decryption: apply checksum after round key (to get decrypted value)
17 | data[i + offset] -= roundKey;
18 | checksum += data[i + offset];
19 | keyIndex = (keyIndex + 1) % keyLength;
20 | }
21 | }
22 |
23 | /**
24 | * Encrypts data using CYCSUB.
25 | */
26 | public static void encrypt(byte[] data, int offset, int length, byte[] key, int keyOffset, int keyLength) {
27 | byte checksum = 0;
28 | int keyIndex = 0;
29 | for (int i = length - 1; i >= 0; i--) {
30 | byte roundKey = (byte) (checksum + key[keyOffset + keyIndex]);
31 | // encryption: apply checksum before round key (to get decrypted value)
32 | checksum += data[i + offset];
33 | data[i + offset] += roundKey;
34 | keyIndex = (keyIndex + 1) % keyLength;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/after/src/main/java/after/algorithms/NQCRC.java:
--------------------------------------------------------------------------------
1 | package after.algorithms;
2 |
3 | /**
4 | * Implementation of NQCRC.
5 | */
6 | public class NQCRC {
7 | private static final int[] nqcrcTable = new int[256];
8 | static {
9 | for (int i = 0; i < nqcrcTable.length; i++) {
10 | int value = i << 0x18;
11 | for (int j = 0; j < 8; j++)
12 | value = (value << 1) ^ (value < 0 ? 0xAF : 0);
13 | nqcrcTable[i] = value;
14 | }
15 | }
16 |
17 | /**
18 | * Advances NQCRC by a single byte.
19 | */
20 | public static int hash(int hash, byte byt) {
21 | return (hash << 8) ^ nqcrcTable[(hash >>> 24) ^ (byt & 0xFF)];
22 | }
23 |
24 | /**
25 | * Advances NQCRC by an array of bytes.
26 | */
27 | public static int hash(int hash, byte[] data) {
28 | for (byte b : data)
29 | hash = hash(hash, b);
30 | return hash;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/after/src/main/java/after/algorithms/RUNSUB.java:
--------------------------------------------------------------------------------
1 | package after.algorithms;
2 |
3 | /**
4 | * Implementation of RUNSUB.
5 | * This implementation operates on whole buffers at a time in-place.
6 | */
7 | public class RUNSUB {
8 | /**
9 | * Decrypts data using RUNSUB.
10 | * The last byte is checked against the checksum but otherwise ignored.
11 | * If the decryption fails, RUNSUBFailedException is thrown, but the data is put in the buffer.
12 | *
13 | * @throws RUNSUBFailedException On checksum failure
14 | */
15 | public static void decrypt(byte[] data, int offset, int length, int key) {
16 | byte checksum = 0;
17 | for (int i = 0; i < length - 1; i++) {
18 | byte roundKey = (byte) (checksum + (key >> (checksum % 32)));
19 | // decryption: apply checksum after round key (to get decrypted value)
20 | data[i + offset] -= roundKey;
21 | checksum += data[i + offset];
22 | }
23 | if (checksum != data[(length - 1) + offset])
24 | throw new RUNSUBFailedException(data[(length - 1) + offset], checksum);
25 | }
26 |
27 | /**
28 | * Encrypts data using RUNSUB.
29 | * The last byte is overwritten with the checksum.
30 | */
31 | public static void encrypt(byte[] data, int offset, int length, int key) {
32 | byte checksum = 0;
33 | for (int i = 0; i < length - 1; i++) {
34 | byte roundKey = (byte) (checksum + (key >> (checksum % 32)));
35 | // encryption: apply checksum before round key (to get decrypted value)
36 | checksum += data[i + offset];
37 | data[i + offset] += roundKey;
38 | }
39 | data[(length - 1) + offset] = checksum;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/after/src/main/java/after/algorithms/RUNSUBFailedException.java:
--------------------------------------------------------------------------------
1 | package after.algorithms;
2 |
3 | /**
4 | * Indicates that a checksum failure happened during RUNSUB decryption.
5 | *
6 | * This is a RuntimeException on the same basis that NumberFormatException is a RuntimeException,
7 | * i.e. that it would be obstructive and annoying to make this a normal Exception.
8 | */
9 | public class RUNSUBFailedException extends RuntimeException {
10 | /**
11 | * The expected checksum: The one written in the file.
12 | */
13 | public final byte expected;
14 |
15 | /**
16 | * The resulting checksum: The actual result.
17 | */
18 | public final byte result;
19 |
20 | public RUNSUBFailedException(byte e, byte g) {
21 | expected = e;
22 | result = g;
23 | }
24 |
25 | @Override
26 | public String getMessage() {
27 | return "RUNSUB decryption failed - stated checksum " + expected + ", got " + result;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/after/src/main/java/after/algorithms/XORJump9.java:
--------------------------------------------------------------------------------
1 | package after.algorithms;
2 |
3 | public class XORJump9 {
4 | /**
5 | * Runs XORJUMP9 on some data.
6 | * @return The modified key.
7 | */
8 | public static byte xorJump9(byte[] data, byte key) {
9 | for (int i = 0; i < data.length; i++) {
10 | data[i] ^= key;
11 | key += 9;
12 | }
13 | return key;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/algorithms/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Encodings and algorithms used in various formats.
3 | */
4 | package after.algorithms;
5 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/CacheFileID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface CacheFileID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/ClassID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface ClassID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/DMBList.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface DMBList {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/HasNoMeaning.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * Marker type for use by DMBValue
12 | */
13 | @Documented
14 | @Retention(RUNTIME)
15 | @Target(FIELD)
16 | public @interface HasNoMeaning {
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/InstanceID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface InstanceID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/ListID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface ListID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/ListOfProcID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface ListOfProcID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/ListOfVarID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface ListOfVarID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/MobTypeID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface MobTypeID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/ProcID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface ProcID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/StringID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | @Documented
11 | @Retention(RUNTIME)
12 | @Target(FIELD)
13 | public @interface StringID {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/VarID.java:
--------------------------------------------------------------------------------
1 | package after.annotations;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * Almost certainly won't be used as an actual annotation.
12 | * Exists as a marker.
13 | */
14 | @Documented
15 | @Retention(RUNTIME)
16 | @Target(FIELD)
17 | public @interface VarID {
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/after/src/main/java/after/annotations/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Marker annotations used by everything except this.
3 | */
4 | package after.annotations;
--------------------------------------------------------------------------------
/after/src/main/java/after/dm/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Utilities for working with DM. In text form.
3 | */
4 | package after.dm;
5 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/CacheID.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | /**
4 | * Represents a Cache ID as used in both the DMB and RSC file formats.
5 | */
6 | public final class CacheID {
7 | public static final CacheID ZERO = new CacheID(0, (byte) 0);
8 |
9 | public final int uniqueID;
10 | public final byte typeOrSomething;
11 |
12 | public CacheID(int uid, byte type) {
13 | uniqueID = uid;
14 | typeOrSomething = type;
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return "(" + typeOrSomething + ", " + uniqueID + ")";
20 | }
21 |
22 | @Override
23 | public int hashCode() {
24 | return uniqueID;
25 | }
26 |
27 | @Override
28 | public boolean equals(Object obj) {
29 | if (!(obj instanceof CacheID))
30 | return false;
31 | CacheID cid = ((CacheID) obj);
32 | return (cid.typeOrSomething == typeOrSomething) && (cid.uniqueID == uniqueID);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMB.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 | import java.nio.ByteBuffer;
5 | import java.nio.ByteOrder;
6 |
7 | import aedium.NonNull;
8 | import after.io.framework.DMBReadContext;
9 | import after.io.framework.DMBWriteContext;
10 |
11 | /**
12 | * This is the root, the DMB file as a whole.
13 | */
14 | public class DMB {
15 |
16 | @NonNull
17 | public DMBHeader header = new DMBHeader();
18 | @NonNull
19 | public DMBGrid grid = new DMBGrid();
20 |
21 | public int totalSizeOfAllStrings;
22 |
23 | @NonNull
24 | public DMBClassTable classes = new DMBClassTable();
25 | @NonNull
26 | public DMBMobTypeTable mobTypes = new DMBMobTypeTable();
27 | @NonNull
28 | public DMBStringTable strings = new DMBStringTable();
29 | @NonNull
30 | public DMBListTable lists = new DMBListTable();
31 | @NonNull
32 | public DMBProcTable procs = new DMBProcTable();
33 | @NonNull
34 | public DMBVarTable vars = new DMBVarTable();
35 | @NonNull
36 | public DMBSubblock7 sb7 = new DMBSubblock7();
37 | @NonNull
38 | public DMBInstanceTable instances = new DMBInstanceTable();
39 | @NonNull
40 | public DMBMapAdditionalData mapAdditionalData = new DMBMapAdditionalData();
41 | @NonNull
42 | public DMBStandardObjectIDs standardObjectIDs = new DMBStandardObjectIDs();
43 | @NonNull
44 | public DMBCacheFileTable cacheFiles = new DMBCacheFileTable();
45 |
46 | public void read(DMBReadContext rc) {
47 | // Sub-Block -1 (Header)
48 | rc.section("header");
49 | header.read(rc);
50 |
51 | // Sub-Block 0 (The Grid)
52 | rc.section("grid");
53 | grid.read(rc);
54 |
55 | // Interlude
56 | rc.section("totalSizeOfAllStrings");
57 | totalSizeOfAllStrings = rc.i32();
58 | if (rc.debug)
59 | System.out.println(" = " + totalSizeOfAllStrings);
60 |
61 | // Classes Table
62 | rc.section("classes");
63 | classes.read(rc);
64 | if (rc.debug)
65 | System.out.println(" = " + classes.entries.size());
66 |
67 | // Mob Type Table
68 | rc.section("mobTypes");
69 | mobTypes.read(rc);
70 | if (rc.debug)
71 | System.out.println(" = " + mobTypes.entries.size());
72 |
73 | // String Table
74 | rc.section("strings");
75 | strings.read(rc);
76 |
77 | // List Table
78 | rc.section("lists");
79 | lists.read(rc);
80 |
81 | // Proc Table
82 | rc.section("procs");
83 | procs.read(rc);
84 |
85 | // Var Table
86 | rc.section("vars");
87 | vars.read(rc);
88 |
89 | // Sub-Block 7
90 | rc.section("sb7");
91 | sb7.read(rc);
92 |
93 | // Instance Table
94 | rc.section("instances");
95 | instances.read(rc);
96 |
97 | // Map Additional Data
98 | rc.section("mapAdditionalData");
99 | mapAdditionalData.read(rc);
100 |
101 | // Map Additional Data
102 | rc.section("standardObjectIDs");
103 | standardObjectIDs.read(rc);
104 |
105 | rc.section("cacheFiles");
106 | cacheFiles.read(rc);
107 |
108 | // -- other unfinished --
109 | rc.section("unfinished");
110 | int unfinished = rc.remaining();
111 | if (unfinished != 0)
112 | if (rc.debug)
113 | System.err.println("warning: ignored 0x" + Integer.toHexString(unfinished));
114 | }
115 |
116 | public void write(DMBWriteContext wc) {
117 | wc.section("header");
118 | header.write(wc);
119 |
120 | wc.section("grid");
121 | grid.write(wc);
122 |
123 | wc.section("totalSizeOfAllStrings");
124 | wc.i32(totalSizeOfAllStrings);
125 |
126 | wc.section("classes");
127 | classes.write(wc);
128 |
129 | wc.section("mobTypes");
130 | mobTypes.write(wc);
131 |
132 | wc.section("strings");
133 | strings.write(wc);
134 |
135 | wc.section("lists");
136 | lists.write(wc);
137 |
138 | wc.section("procs");
139 | procs.write(wc);
140 |
141 | wc.section("vars");
142 | vars.write(wc);
143 |
144 | wc.section("sb7");
145 | sb7.write(wc);
146 |
147 | wc.section("instances");
148 | instances.write(wc);
149 |
150 | wc.section("mapAdditionalData");
151 | mapAdditionalData.write(wc);
152 |
153 | wc.section("standardObjectIDs");
154 | standardObjectIDs.write(wc);
155 |
156 | wc.section("cacheFiles");
157 | cacheFiles.write(wc);
158 | }
159 |
160 | /**
161 | * Fixes the string table stuff after it's been twiddled with
162 | */
163 | public void fixStrings() {
164 | strings.hash = strings.calculateHash();
165 | totalSizeOfAllStrings = strings.calculateTotalSize();
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBCacheFileTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import after.io.framework.DMBReadContext;
4 | import after.io.framework.DMBWriteContext;
5 |
6 | /**
7 | * References to resources.
8 | */
9 | public class DMBCacheFileTable extends DMBEntryBasedSubblock {
10 | @Override
11 | protected CacheID createDummyValue() {
12 | return CacheID.ZERO;
13 | }
14 |
15 | @Override
16 | public CacheID readEntry(DMBReadContext rc) {
17 | int uniqueID = rc.i32();
18 | byte typeOrSomething = rc.i8();
19 | return new CacheID(uniqueID, typeOrSomething);
20 | }
21 |
22 | @Override
23 | public void writeEntry(DMBWriteContext wc, CacheID entry) {
24 | wc.i32(entry.uniqueID);
25 | wc.i8(entry.typeOrSomething);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBClassTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 |
5 | import after.annotations.CacheFileID;
6 | import after.annotations.ClassID;
7 | import after.annotations.ListID;
8 | import after.annotations.ListOfProcID;
9 | import after.annotations.ProcID;
10 | import after.annotations.StringID;
11 | import after.io.framework.DMBReadContext;
12 | import after.io.framework.DMBWriteContext;
13 |
14 | public class DMBClassTable extends DMBObjectEntryBasedSubblock {
15 |
16 | @Override
17 | public Entry make() {
18 | return new Entry();
19 | }
20 |
21 | public static class Entry implements DMBObjectEntryBasedSubblock.Entry {
22 | public static final int CF_MOB = 2;
23 | public static final int CF_ATOM = 4;
24 | public static final int CF_AREA = 8;
25 |
26 | @StringID
27 | public int name;
28 | @ClassID // Nullable
29 | public int parent = OBJ_NULL;
30 | @StringID // Nullable
31 | public int objName = OBJ_NULL;
32 | @StringID // Nullable
33 | public int description = OBJ_NULL;
34 | @CacheFileID // Nullable
35 | public int icon = OBJ_NULL;
36 | @StringID // Nullable
37 | public int iconState = OBJ_NULL;
38 |
39 | // need to figure this out too
40 | public byte direction = 2;
41 |
42 | // >= 307
43 |
44 | public boolean dmSpecialTypeLong;
45 | public int dmSpecialType = 1;
46 |
47 | // --
48 |
49 | @StringID // maybe nullable
50 | public int text = OBJ_NULL;
51 |
52 | // >= 494
53 |
54 | @StringID // Nullable
55 | public int maptext;
56 | public short maptextWidth;
57 | public short maptextHeight;
58 |
59 | // >= 508
60 |
61 | public short maptextX;
62 | public short maptextY;
63 |
64 | // --
65 |
66 | @StringID // Nullable
67 | public int suffix = OBJ_NULL;
68 | // see CF_* constants
69 | public int flags;
70 | @ListOfProcID // Nullable (not the elements)
71 | public int verbTable = OBJ_NULL;
72 | @ListOfProcID // Nullable (not the elements)
73 | public int procTable = OBJ_NULL;
74 | @ProcID // Nullable
75 | public int initializer = OBJ_NULL;
76 | @ListID // Marked Nullable because honestly it probably is, but unsure
77 | public int initializedVarsTable = OBJ_NULL;
78 | @ListID // Nullable, (complex key/value pairs thing, see docs)
79 | public int varTable = OBJ_NULL;
80 |
81 | // >= 267
82 |
83 | public float layer;
84 |
85 | // >= 500
86 |
87 | public byte hasTransform;
88 | public float[] transform = new float[6];
89 |
90 | // >= 509
91 |
92 | public byte hasColourMatrix;
93 | public float[] colourMatrix = new float[20];
94 |
95 | // >= 306
96 |
97 | @ListID // Nullable, complex key/value pairs thing
98 | public int overridingVarList = OBJ_NULL;
99 |
100 | @Override
101 | public void read(DMBReadContext rc) {
102 | name = rc.id();
103 | parent = rc.id();
104 | objName = rc.id();
105 | description = rc.id();
106 | icon = rc.id();
107 | iconState = rc.id();
108 | direction = rc.io.get();
109 |
110 | if (rc.vGEN >= 307) {
111 | dmSpecialType = rc.io.get();
112 | dmSpecialTypeLong = dmSpecialType == 0x0F;
113 | if (dmSpecialTypeLong)
114 | dmSpecialType = rc.io.getInt();
115 | } else {
116 | dmSpecialType = 1;
117 | }
118 |
119 | text = rc.id();
120 |
121 | if (rc.vRHS >= 494) {
122 | maptext = rc.id();
123 | maptextWidth = rc.io.getShort();
124 | maptextHeight = rc.io.getShort();
125 | }
126 |
127 | if (rc.vRHS >= 508) {
128 | maptextX = rc.io.getShort();
129 | maptextY = rc.io.getShort();
130 | }
131 |
132 | suffix = rc.id();
133 | if (rc.vGEN >= 306) {
134 | flags = rc.io.getInt();
135 | } else {
136 | flags = rc.io.get() & 0xFF;
137 | }
138 | verbTable = rc.id();
139 | procTable = rc.id();
140 | initializer = rc.id();
141 | initializedVarsTable = rc.id();
142 | varTable = rc.id();
143 |
144 | if (rc.vGEN >= 267)
145 | layer = rc.io.getFloat();
146 |
147 | if (rc.vRHS >= 500) {
148 | hasTransform = rc.io.get();
149 | if (hasTransform != 0)
150 | rc.floats(transform);
151 | }
152 |
153 | if (rc.vRHS >= 509) {
154 | hasColourMatrix = rc.io.get();
155 | if (hasColourMatrix != 0)
156 | rc.floats(colourMatrix);
157 | }
158 |
159 | if (rc.vGEN >= 306)
160 | overridingVarList = rc.id();
161 | }
162 |
163 | @Override
164 | public void write(DMBWriteContext wc) {
165 | wc.id(name);
166 | wc.id(parent);
167 | wc.id(objName);
168 | wc.id(description);
169 | wc.id(icon);
170 | wc.id(iconState);
171 | wc.i8(direction);
172 |
173 | if (wc.vGEN >= 307) {
174 | if (dmSpecialTypeLong) {
175 | wc.i8(0x0F);
176 | wc.i32(dmSpecialType);
177 | } else {
178 | wc.i8(dmSpecialType);
179 | }
180 | }
181 |
182 | wc.id(text);
183 |
184 | if (wc.vRHS >= 494) {
185 | wc.id(maptext);
186 | wc.i16(maptextWidth);
187 | wc.i16(maptextHeight);
188 | }
189 |
190 | if (wc.vRHS >= 508) {
191 | wc.i16(maptextX);
192 | wc.i16(maptextY);
193 | }
194 |
195 | wc.id(suffix);
196 | if (wc.vGEN >= 306) {
197 | wc.i32(flags);
198 | } else {
199 | wc.i8(flags);
200 | }
201 | wc.id(verbTable);
202 | wc.id(procTable);
203 | wc.id(initializer);
204 | wc.id(initializedVarsTable);
205 | wc.id(varTable);
206 | if (wc.vGEN >= 267)
207 | wc.i32(Float.floatToIntBits(layer));
208 |
209 | if (wc.vRHS >= 500) {
210 | wc.i8(hasTransform);
211 | if (hasTransform != 0)
212 | wc.floats(transform);
213 | }
214 |
215 | if (wc.vRHS >= 509) {
216 | wc.i8(hasColourMatrix);
217 | if (hasColourMatrix != 0)
218 | wc.floats(colourMatrix);
219 | }
220 |
221 | if (wc.vGEN >= 306)
222 | wc.id(overridingVarList);
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBEntryBasedSubblock.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.util.LinkedList;
4 |
5 | import after.io.framework.DMBReadContext;
6 | import after.io.framework.DMBWriteContext;
7 |
8 | /**
9 | * This implements the basic 'list of X' logic. This is NOT exclusive with having other properties.
10 | */
11 | public abstract class DMBEntryBasedSubblock {
12 | public static int OBJ_NULL = 0xFFFF;
13 |
14 | /**
15 | * The list of entries. This is a linked list for easier manipulation.
16 | */
17 | public LinkedList entries = new LinkedList<>();
18 |
19 | public DMBEntryBasedSubblock() {
20 | super();
21 | }
22 |
23 | protected abstract X createDummyValue();
24 |
25 | /**
26 | * Indicates if a given index is reserved.
27 | */
28 | public boolean indexReserved(int index) {
29 | return index == OBJ_NULL;
30 | }
31 |
32 | protected int readCount(DMBReadContext rc) {
33 | return rc.id();
34 | }
35 |
36 | public void read(DMBReadContext rc) {
37 | int count = readCount(rc);
38 | for (int i = 0; i < count; i++)
39 | entries.add(readEntry(rc));
40 | }
41 |
42 | protected void writeCount(DMBWriteContext wc) {
43 | wc.id(entries.size());
44 | }
45 |
46 | public void write(DMBWriteContext wc) {
47 | writeCount(wc);
48 | for (X entry : entries)
49 | writeEntry(wc, entry);
50 | }
51 |
52 | public int add(X value) {
53 | int res = entries.size();
54 | while (indexReserved(res)) {
55 | entries.add(createDummyValue());
56 | res++;
57 | }
58 | entries.add(value);
59 | return res;
60 | }
61 |
62 | public abstract X readEntry(DMBReadContext rc);
63 | public abstract void writeEntry(DMBWriteContext wc, X entry);
64 | }
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBGrid.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 |
5 | import aedium.ElementMarkers;
6 | import aedium.NonNull;
7 | import after.annotations.InstanceID;
8 | import after.io.framework.DMBReadContext;
9 | import after.io.framework.DMBWriteContext;
10 |
11 | /**
12 | * This represents the '3D' map.
13 | */
14 | public class DMBGrid {
15 | public int xSize = 0, ySize = 0, zSize = 0;
16 | // Array of nullable (0xFFFF) InstanceIDs
17 | @ElementMarkers({InstanceID.class})
18 | @NonNull
19 | public int[] turf = new int[0];
20 | // Array of nullable (0xFFFF) InstanceIDs
21 | @ElementMarkers({InstanceID.class})
22 | @NonNull
23 | public int[] area = new int[0];
24 | // Array of nullable (0xFFFF) ListIDs of ObjectIDs
25 | @NonNull
26 | public int[] additionalTurfs = new int[0];
27 |
28 | public void read(DMBReadContext rc) {
29 | xSize = rc.io.getShort() & 0xFFFF;
30 | ySize = rc.io.getShort() & 0xFFFF;
31 | zSize = rc.io.getShort() & 0xFFFF;
32 | int total = xSize * ySize * zSize;
33 | turf = new int[total];
34 | area = new int[total];
35 | additionalTurfs = new int[total];
36 |
37 | int i = 0;
38 | while (i < total) {
39 | turf[i] = rc.id();
40 | area[i] = rc.id();
41 | additionalTurfs[i] = rc.id();
42 | int dup = rc.io.get() & 0xFF;
43 | for (int j = 0; j < dup; j++) {
44 | turf[i + j] = turf[i];
45 | area[i + j] = area[i];
46 | additionalTurfs[i + j] = additionalTurfs[i];
47 | }
48 | i += dup;
49 | }
50 | }
51 |
52 | public void write(DMBWriteContext wc) {
53 | wc.i16(xSize);
54 | wc.i16(ySize);
55 | wc.i16(zSize);
56 | int total = xSize * ySize * zSize;
57 | int rleStart = -1;
58 | for (int i = 0; i < total; i++) {
59 | boolean reset = false;
60 | // reset conditions {
61 | // ensure that an RLE length can never exceed 255
62 | if (i - rleStart == 255)
63 | reset = true;
64 | // detect if an RLE change occurred
65 | if (rleStart != -1) {
66 | if (turf[i] != turf[rleStart])
67 | reset = true;
68 | else if (area[i] != area[rleStart])
69 | reset = true;
70 | else if (additionalTurfs[i] != additionalTurfs[rleStart])
71 | reset = true;
72 | }
73 | // actually apply resets
74 | if (reset) {
75 | wc.i8(i - rleStart);
76 | rleStart = -1;
77 | }
78 | // if not extending (this doesn't necessarily mean a reset happened, for first tile), write & start
79 | if (rleStart == -1) {
80 | wc.id(turf[i]);
81 | wc.id(area[i]);
82 | wc.id(additionalTurfs[i]);
83 | rleStart = i;
84 | }
85 | }
86 | if (rleStart != -1)
87 | wc.i8(total - rleStart);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBHeader.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import aedium.NonNull;
7 | import after.io.framework.DMBContext;
8 | import after.io.framework.DMBReadContext;
9 | import after.io.framework.DMBWriteContext;
10 |
11 | public class DMBHeader {
12 | // These are converted in Latin-1 style for lack of any better ideas
13 | public String shebang = null;
14 |
15 | private static int DEFAULT_VERSION = 230;
16 |
17 | public int vGEN = DEFAULT_VERSION, vLHS = DEFAULT_VERSION, vRHS = DEFAULT_VERSION;
18 | public int flags = 0;
19 | public int exFlags;
20 |
21 | public void read(DMBReadContext rc) {
22 | StringBuilder lineText = new StringBuilder();
23 | boolean compatLine = false;
24 | shebang = "";
25 | while (true) {
26 | byte b = rc.io.get();
27 | if (b == 10) {
28 | String text = lineText.toString();
29 | if (!compatLine) {
30 | if (text.startsWith("#")) {
31 | shebang += text + "\n";
32 | // move the base pointer forward to the next line
33 | // if there's more comment lines, this keeps happening
34 | rc.basePointer = rc.io.position();
35 | } else {
36 | vLHS = vRHS = vGEN = Integer.parseInt(text.split(" v")[1]);
37 | compatLine = true;
38 | }
39 | } else {
40 | String[] versionBits = text.split(" v")[1].split(" ");
41 | if (versionBits.length < 2) {
42 | int v = Integer.parseInt(versionBits[0]);
43 | vLHS = vRHS = v;
44 | } else {
45 | vLHS = Integer.parseInt(versionBits[0]);
46 | vRHS = Integer.parseInt(versionBits[1]);
47 | }
48 | // Done
49 | break;
50 | }
51 | lineText = new StringBuilder();
52 | } else {
53 | lineText.append((char) (b & 0xFF));
54 | }
55 | }
56 |
57 | flags = rc.io.getInt();
58 | exFlags = 0;
59 | if ((flags & 0x80000000) != 0)
60 | exFlags = rc.io.getInt();
61 |
62 | updateContext(rc);
63 |
64 | if (rc.debug) {
65 | System.out.println(" version " + vGEN + " " + vLHS + " " + vRHS);
66 | System.out.println(" large object IDs = " + rc.largeObjectIDs);
67 | System.out.println(" base pointer = " + rc.basePointer);
68 | }
69 | }
70 |
71 | public void write(DMBWriteContext wc) {
72 | if (shebang != null)
73 | wc.bytes(shebang.getBytes(StandardCharsets.ISO_8859_1));
74 | wc.basePointer = wc.position;
75 | wc.bytes(("world bin v" + vGEN).getBytes(StandardCharsets.ISO_8859_1));
76 | wc.i8(10);
77 | wc.bytes(("min compatibility v" + vLHS + " " + vRHS).getBytes(StandardCharsets.ISO_8859_1));
78 | wc.i8(10);
79 |
80 | wc.i32(flags);
81 | if ((flags & 0x80000000) != 0)
82 | wc.i32(exFlags);
83 |
84 | updateContext(wc);
85 | }
86 |
87 | private void updateContext(DMBContext c) {
88 | c.vGEN = vGEN;
89 | c.vLHS = vLHS;
90 | c.vRHS = vRHS;
91 | c.largeObjectIDs = (flags & 0x40000000) != 0;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBInstanceTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 |
5 | import after.annotations.ClassID;
6 | import after.annotations.ProcID;
7 | import after.io.framework.DMBReadContext;
8 | import after.io.framework.DMBWriteContext;
9 |
10 | public class DMBInstanceTable extends DMBObjectEntryBasedSubblock {
11 | @Override
12 | public Entry make() {
13 | return new Entry();
14 | }
15 |
16 | public static class Entry implements DMBObjectEntryBasedSubblock.Entry {
17 | public DMBValue value = DMBValue.NULL_VALUE;
18 | @ProcID // Nullable
19 | public int initializer = OBJ_NULL;
20 |
21 | @Override
22 | public void read(DMBReadContext rc) {
23 | byte bt = rc.io.get();
24 | int v = rc.io.getInt();
25 | value = new DMBValue(bt, v);
26 | initializer = rc.id();
27 | }
28 |
29 | @Override
30 | public void write(DMBWriteContext wc) {
31 | wc.i8(value.type);
32 | wc.i32(value.value);
33 | wc.id(initializer);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBListTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.util.LinkedList;
4 |
5 | import after.io.framework.DMBReadContext;
6 | import after.io.framework.DMBWriteContext;
7 |
8 | /**
9 | * Lists of arrays of ObjectIDs.
10 | */
11 | public class DMBListTable extends DMBEntryBasedSubblock {
12 | @Override
13 | protected int[] createDummyValue() {
14 | return new int[0];
15 | }
16 |
17 | @Override
18 | public int[] readEntry(DMBReadContext rc) {
19 | int[] ie = new int[rc.io.getShort() & 0xFFFF];
20 | rc.ids(ie);
21 | return ie;
22 | }
23 |
24 | @Override
25 | public void writeEntry(DMBWriteContext wc, int[] entry) {
26 | wc.i16(entry.length);
27 | wc.ids(entry);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBMapAdditionalData.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import after.io.framework.DMBReadContext;
4 | import after.io.framework.DMBWriteContext;
5 |
6 | public class DMBMapAdditionalData extends DMBObjectEntryBasedSubblock {
7 | @Override
8 | public Entry make() {
9 | return new Entry();
10 | }
11 |
12 | @Override
13 | protected int readCount(DMBReadContext rc) {
14 | return rc.io.getInt();
15 | }
16 |
17 | @Override
18 | protected void writeCount(DMBWriteContext wc) {
19 | wc.i32(entries.size());
20 | }
21 |
22 | public static class Entry implements DMBObjectEntryBasedSubblock.Entry {
23 | // Offset from last entry
24 | public short offset;
25 | // Nullable InstanceID
26 | public int instance = DMBObjectEntryBasedSubblock.OBJ_NULL;
27 |
28 | @Override
29 | public void read(DMBReadContext rc) {
30 | offset = rc.io.getShort();
31 | instance = rc.id();
32 | }
33 |
34 | @Override
35 | public void write(DMBWriteContext wc) {
36 | wc.i16(offset);
37 | wc.id(instance);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBMobTypeTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 |
5 | import after.annotations.ClassID;
6 | import after.annotations.StringID;
7 | import after.io.framework.DMBReadContext;
8 | import after.io.framework.DMBWriteContext;
9 |
10 | public class DMBMobTypeTable extends DMBObjectEntryBasedSubblock {
11 | @Override
12 | public Entry make() {
13 | return new Entry();
14 | }
15 |
16 | public static class Entry implements DMBObjectEntryBasedSubblock.Entry {
17 | @ClassID
18 | public int clazz;
19 | @StringID // Nullable
20 | public int key;
21 | public byte sightFlags;
22 | public int sightFlagsEx;
23 | public byte seeInDark;
24 | public byte seeInvisible;
25 |
26 | @Override
27 | public void read(DMBReadContext rc) {
28 | clazz = rc.id();
29 | key = rc.id();
30 | sightFlags = rc.io.get();
31 | if (sightFlags < 0) {
32 | sightFlagsEx = rc.io.getInt();
33 | seeInDark = rc.io.get();
34 | seeInvisible = rc.io.get();
35 | }
36 | }
37 |
38 | @Override
39 | public void write(DMBWriteContext wc) {
40 | wc.id(clazz);
41 | wc.id(key);
42 | wc.i8(sightFlags);
43 | if (sightFlags < 0) {
44 | wc.i32(sightFlagsEx);
45 | wc.i8(seeInDark);
46 | wc.i8(seeInvisible);
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBObjectEntryBasedSubblock.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.util.LinkedList;
4 |
5 | import after.io.framework.DMBReadContext;
6 | import after.io.framework.DMBWriteContext;
7 |
8 | public abstract class DMBObjectEntryBasedSubblock extends DMBEntryBasedSubblock {
9 | @Override
10 | protected X createDummyValue() {
11 | return make();
12 | }
13 |
14 | @Override
15 | public X readEntry(DMBReadContext rc) {
16 | X entry = make();
17 | entry.read(rc);
18 | return entry;
19 | }
20 |
21 | @Override
22 | public void writeEntry(DMBWriteContext rc, X entry) {
23 | entry.write(rc);
24 | }
25 |
26 | public abstract X make();
27 |
28 | public static interface Entry {
29 | void read(DMBReadContext rc);
30 | void write(DMBWriteContext wc);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBProcTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 |
5 | import after.annotations.ListID;
6 | import after.annotations.ListOfVarID;
7 | import after.annotations.StringID;
8 | import after.io.framework.DMBReadContext;
9 | import after.io.framework.DMBWriteContext;
10 |
11 | public class DMBProcTable extends DMBObjectEntryBasedSubblock {
12 | @Override
13 | public Entry make() {
14 | return new Entry();
15 | }
16 |
17 | public static class Entry implements DMBObjectEntryBasedSubblock.Entry {
18 | // GEN >= 224 | LO
19 | @StringID // Nullable
20 | public int path = OBJ_NULL;
21 | // --
22 | @StringID // Nullable
23 | public int name = OBJ_NULL;
24 | @StringID // Nullable
25 | public int desc = OBJ_NULL;
26 | @StringID // Nullable
27 | public int verbCategory = OBJ_NULL;
28 | public byte verbSrcParam = -1;
29 | public byte verbSrcKind;
30 | public byte flags;
31 | public int flagsExx;
32 | public byte flagsExy;
33 | // ListID (list containing DM bytecode)
34 | @ListID
35 | public int code = OBJ_NULL; // fill these with NULL so that the 0 doesn't get used by mistake
36 | @ListOfVarID
37 | public int locals = OBJ_NULL;
38 | @ListID
39 | public int args = OBJ_NULL;
40 |
41 | @Override
42 | public void read(DMBReadContext rc) {
43 | if (rc.vGEN >= 224 || rc.largeObjectIDs)
44 | path = rc.id();
45 | name = rc.id();
46 | desc = rc.id();
47 | verbCategory = rc.id();
48 | verbSrcParam = rc.io.get();
49 | verbSrcKind = rc.io.get();
50 | flags = rc.io.get();
51 | if (flags < 0) {
52 | flagsExx = rc.io.getInt();
53 | flagsExy = rc.io.get();
54 | }
55 | code = rc.id();
56 | locals = rc.id();
57 | args = rc.id();
58 | }
59 |
60 | @Override
61 | public void write(DMBWriteContext wc) {
62 | if (wc.vGEN >= 224 || wc.largeObjectIDs)
63 | wc.id(path);
64 | wc.id(name);
65 | wc.id(desc);
66 | wc.id(verbCategory);
67 | wc.i8(verbSrcParam);
68 | wc.i8(verbSrcKind);
69 | wc.i8(flags);
70 | if (flags < 0) {
71 | wc.i32(flagsExx);
72 | wc.i8(flagsExy);
73 | }
74 | wc.id(code);
75 | wc.id(locals);
76 | wc.id(args);
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBStandardObjectIDs.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import after.annotations.CacheFileID;
4 | import after.annotations.ClassID;
5 | import after.annotations.ListOfProcID;
6 | import after.annotations.MobTypeID;
7 | import after.annotations.ProcID;
8 | import after.annotations.StringID;
9 | import after.io.framework.DMBReadContext;
10 | import after.io.framework.DMBWriteContext;
11 | import static after.io.DMBObjectEntryBasedSubblock.OBJ_NULL;
12 |
13 | import aedium.ElementMarkers;
14 |
15 | public class DMBStandardObjectIDs {
16 | @MobTypeID // Nullable
17 | public int mob = OBJ_NULL;
18 | // Nullable ClassID
19 | @ClassID // Nullable
20 | public int turf = OBJ_NULL;
21 | @ClassID // Nullable
22 | public int area = OBJ_NULL;
23 | @ListOfProcID
24 | public int procs = OBJ_NULL;
25 | @ProcID // Nullable
26 | public int globalVariableInitializer = OBJ_NULL;
27 | @StringID // Nullable
28 | public int domain = OBJ_NULL;
29 | @StringID
30 | public int name = OBJ_NULL;
31 | // GEN < 368 (yes, under)
32 | public int idOld1 = OBJ_NULL;
33 | // --
34 | public int tickTimeMillis = 100;
35 | @ClassID
36 | public int client = OBJ_NULL;
37 | // GEN >= 308
38 | @ClassID
39 | public int image = OBJ_NULL;
40 | // --
41 | public byte clientLazyEye;
42 | public byte clientDir = 1;
43 | // GEN >= 415
44 | public short clientControlFreak;
45 | // --
46 | public byte unkE;
47 | // GEN >= 230
48 | @StringID // Nullable
49 | public int clientScript = OBJ_NULL;
50 | // --
51 | // GEN >= 507
52 | @ElementMarkers(CacheFileID.class)
53 | public int[] clientScriptFiles = new int[0];
54 | // GEN < 507 (yes, under)
55 | public int idX = OBJ_NULL;
56 | // GEN >= 232
57 | public short unkF = 2827;
58 | // GEN >= 235 && GEN < 368
59 | public short unkPX;
60 | // GEN >= 236 && GEN < 368
61 | public short unkPY;
62 | // GEN >= 341
63 | @StringID // Nullable
64 | public int hubPasswordHashed = OBJ_NULL;
65 | // GEN >= 266
66 | @StringID // Nullable
67 | public int serverName = OBJ_NULL;
68 | public int hubNumber;
69 | public int gameVersion;
70 | // GEN >= 272
71 | public short cacheLifespan = 30;
72 | @StringID // Nullable
73 | public int clientCommandText;
74 | @StringID // Nullable
75 | public int clientCommandPrompt;
76 | // GEN >= 276
77 | @StringID // Nullable
78 | public int hub = OBJ_NULL;
79 | // GEN >= 305
80 | @StringID // Nullable (?) but really should be "default"
81 | public int channel = OBJ_NULL;
82 | // GEN >= 360
83 | @CacheFileID // Nullable
84 | public int skin = OBJ_NULL;
85 |
86 | // LHS >= 455
87 | public short iconSizeX = 32;
88 | public short iconSizeY = 32;
89 | public short mapFormat = (short) 32768;
90 |
91 | public void read(DMBReadContext rc) {
92 | mob = rc.id();
93 | turf = rc.id();
94 | area = rc.id();
95 | procs = rc.id();
96 | globalVariableInitializer = rc.id();
97 | domain = rc.id();
98 | name = rc.id();
99 | if (rc.vGEN < 368)
100 | idOld1 = rc.id();
101 | tickTimeMillis = rc.io.getInt();
102 | client = rc.id();
103 | if (rc.vGEN >= 308)
104 | image = rc.id();
105 | clientLazyEye = rc.io.get();
106 | clientDir = rc.io.get();
107 | if (rc.vGEN >= 415)
108 | clientControlFreak = rc.io.getShort();
109 | unkE = rc.io.get();
110 | if (rc.vGEN >= 230)
111 | clientScript = rc.id();
112 | if (rc.vGEN >= 507) {
113 | clientScriptFiles = new int[rc.io.getShort() & 0xFFFF];
114 | rc.ids(clientScriptFiles);
115 | }
116 | if (rc.vGEN < 507) {
117 | idX = rc.id();
118 | }
119 | if (rc.vGEN >= 232)
120 | unkF = rc.io.getShort();
121 | if (rc.vGEN >= 235 && rc.vGEN < 368)
122 | unkPX = rc.io.getShort();
123 | if (rc.vGEN >= 236 && rc.vGEN < 368)
124 | unkPY = rc.io.getShort();
125 | if (rc.vGEN >= 341)
126 | hubPasswordHashed = rc.id();
127 | if (rc.vGEN >= 266) {
128 | serverName = rc.id();
129 | hubNumber = rc.io.getInt();
130 | gameVersion = rc.io.getInt();
131 | }
132 | if (rc.vGEN >= 272) {
133 | cacheLifespan = rc.io.getShort();
134 | clientCommandText = rc.id();
135 | clientCommandPrompt = rc.id();
136 | }
137 | if (rc.vGEN >= 276)
138 | hub = rc.id();
139 | if (rc.vGEN >= 305)
140 | channel = rc.id();
141 | if (rc.vGEN >= 360)
142 | skin = rc.id();
143 |
144 | if (rc.vLHS >= 455) {
145 | iconSizeX = rc.io.getShort();
146 | iconSizeY = rc.io.getShort();
147 | mapFormat = rc.io.getShort();
148 | } else {
149 | iconSizeX = 32;
150 | iconSizeY = 32;
151 | mapFormat = (short) 32768;
152 | }
153 | }
154 |
155 | public void write(DMBWriteContext wc) {
156 | wc.id(mob);
157 | wc.id(turf);
158 | wc.id(area);
159 | wc.id(procs);
160 | wc.id(globalVariableInitializer);
161 | wc.id(domain);
162 | wc.id(name);
163 | if (wc.vGEN < 368)
164 | wc.id(idOld1);
165 | wc.i32(tickTimeMillis);
166 | wc.id(client);
167 | if (wc.vGEN >= 308)
168 | wc.id(image);
169 | wc.i8(clientLazyEye);
170 | wc.i8(clientDir);
171 | if (wc.vGEN >= 415)
172 | wc.i16(clientControlFreak);
173 | wc.i8(unkE);
174 | if (wc.vGEN >= 230)
175 | wc.id(clientScript);
176 | if (wc.vGEN >= 507) {
177 | wc.i16(clientScriptFiles.length);
178 | wc.ids(clientScriptFiles);
179 | }
180 | if (wc.vGEN < 507)
181 | wc.id(idX);
182 | if (wc.vGEN >= 232)
183 | wc.i16(unkF);
184 | if (wc.vGEN >= 235 && wc.vGEN < 368)
185 | wc.i16(unkPX);
186 | if (wc.vGEN >= 236 && wc.vGEN < 368)
187 | wc.i16(unkPY);
188 | if (wc.vGEN >= 341)
189 | wc.id(hubPasswordHashed);
190 | if (wc.vGEN >= 266) {
191 | wc.id(serverName);
192 | wc.i32(hubNumber);
193 | wc.i32(gameVersion);
194 | }
195 | if (wc.vGEN >= 266) {
196 | wc.i16(cacheLifespan);
197 | wc.id(clientCommandText);
198 | wc.id(clientCommandPrompt);
199 | }
200 | if (wc.vGEN >= 276)
201 | wc.id(hub);
202 | if (wc.vGEN >= 305)
203 | wc.id(channel);
204 | if (wc.vGEN >= 360)
205 | wc.id(skin);
206 | if (wc.vLHS >= 455) {
207 | wc.i16(iconSizeX);
208 | wc.i16(iconSizeY);
209 | wc.i16(mapFormat);
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBStringTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.LinkedList;
5 |
6 | import after.algorithms.NQCRC;
7 | import after.algorithms.XORJump9;
8 | import after.io.framework.DMBReadContext;
9 | import after.io.framework.DMBWriteContext;
10 |
11 | /**
12 | * The DM string table.
13 | */
14 | public class DMBStringTable extends DMBEntryBasedSubblock {
15 | /**
16 | * The NQCRC hash of the string table. Use calculateHash to get the correct value for this.
17 | */
18 | public int hash = -1;
19 |
20 | /**
21 | * Gets (doesn't apply) the correct hash value.
22 | */
23 | public int calculateHash() {
24 | int value = -1;
25 | for (byte[] entry : entries) {
26 | value = NQCRC.hash(value, entry);
27 | value = NQCRC.hash(value, (byte) 0);
28 | }
29 | return value;
30 | }
31 |
32 | /**
33 | * Gets (doesn't apply) the total size of all strings (you'll need this)
34 | */
35 | public int calculateTotalSize() {
36 | int value = 0;
37 | for (byte[] entry : entries)
38 | value += entry.length + 1;
39 | return value;
40 | }
41 |
42 | @Override
43 | public boolean indexReserved(int index) {
44 | // Try extremely hard to avoid hitting the subvar strings.
45 | if ((index >= 0xFFC0) && (index <= 0xFFFF))
46 | return true;
47 | return super.indexReserved(index);
48 | }
49 |
50 | @Override
51 | public void read(DMBReadContext rc) {
52 | super.read(rc);
53 | if (rc.vGEN >= 468)
54 | hash = rc.io.getInt();
55 | }
56 |
57 | @Override
58 | protected byte[] createDummyValue() {
59 | return new byte[0];
60 | }
61 |
62 | @Override
63 | public byte[] readEntry(DMBReadContext rc) {
64 | int totalLength = 0;
65 | while (true) {
66 | short val = (short) (rc.io.position() - rc.basePointer);
67 | val ^= rc.io.getShort();
68 | totalLength += val & 0xFFFF;
69 | if (val != (short) -1)
70 | break;
71 | }
72 | byte[] data = new byte[totalLength];
73 | byte key = (byte) (rc.io.position() - rc.basePointer);
74 | rc.io.get(data);
75 | XORJump9.xorJump9(data, key);
76 | return data;
77 | }
78 |
79 | @Override
80 | public void write(DMBWriteContext wc) {
81 | super.write(wc);
82 | if (wc.vGEN >= 468)
83 | wc.i32(hash);
84 | }
85 |
86 | @Override
87 | public void writeEntry(DMBWriteContext wc, byte[] str) {
88 | int remainingLength = str.length;
89 | while (remainingLength >= 0xFFFF) {
90 | wc.i16((wc.position - wc.basePointer) ^ 0xFFFF);
91 | remainingLength -= 0xFFFF;
92 | }
93 | wc.i16((wc.position - wc.basePointer) ^ remainingLength);
94 |
95 | byte[] data = new byte[str.length];
96 | System.arraycopy(str, 0, data, 0, str.length);
97 | byte key = (byte) (wc.position - wc.basePointer);
98 | XORJump9.xorJump9(data, key);
99 | wc.bytes(data);
100 | }
101 |
102 | // -- Utilities --
103 |
104 | /**
105 | * Deliberately ignores Unicode for lossless conversion.
106 | */
107 | public static String fromWTF(byte[] dat) {
108 | return new String(dat, StandardCharsets.ISO_8859_1);
109 | }
110 |
111 | public static boolean equal(byte[] a, byte[] b) {
112 | if (a.length != b.length)
113 | return false;
114 | for (int i = 0; i < a.length; i++)
115 | if (a[i] != b[i])
116 | return false;
117 | return true;
118 | }
119 |
120 | /**
121 | * Gets a string by the "internal" value.
122 | * Uses ISO\_8859_1 to guarantee lossless conversion.
123 | */
124 | public String getString(int i) {
125 | return fromWTF(entries.get(i));
126 | }
127 |
128 | /**
129 | * Sets a string by the "internal" value.
130 | * Potentially not lossless if you were to put Unicode into it.
131 | * Otherwise, uses ISO\_8859_1 to guarantee lossless conversion.
132 | */
133 | public void setString(int i, String val) {
134 | entries.set(i, val.getBytes(StandardCharsets.ISO_8859_1));
135 | }
136 |
137 | /**
138 | * Gets a string ID by content or makes one.
139 | * Expects the content to be a new byte array that doesn't get modified.
140 | */
141 | public int of(byte[] val) {
142 | int idx = 0;
143 | for (byte[] ent : entries) {
144 | if (val.length == ent.length) {
145 | int i;
146 | for (i = 0; i < val.length; i++)
147 | if (val[i] != ent[i])
148 | break;
149 | if (i == val.length)
150 | return idx;
151 | }
152 | idx++;
153 | }
154 | entries.add(val);
155 | return idx;
156 | }
157 |
158 | /**
159 | * Gets a string ID by content or makes one.
160 | */
161 | public int of(String val) {
162 | return of(val.getBytes(StandardCharsets.ISO_8859_1));
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBSubblock7.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 | import java.util.LinkedList;
5 |
6 | import aedium.ElementMarkers;
7 | import aedium.NonNull;
8 | import after.annotations.ClassID;
9 | import after.annotations.ProcID;
10 | import after.io.framework.DMBReadContext;
11 | import after.io.framework.DMBWriteContext;
12 |
13 | public class DMBSubblock7 {
14 | // list of ProcIDs
15 | @ElementMarkers({ProcID.class})
16 | @NonNull
17 | public LinkedList entries = new LinkedList<>();
18 |
19 | public void print(PrintStream ps, DMB base) {
20 | ps.println("# sb7");
21 | int index = 0;
22 | for (Integer ent : entries) {
23 | ps.println(index + ": " + ent);
24 | index++;
25 | }
26 | }
27 |
28 | public void read(DMBReadContext rc) {
29 | int count = rc.id();
30 | for (int i = 0; i < count; i++)
31 | entries.add(rc.id());
32 | }
33 |
34 | public void write(DMBWriteContext wc) {
35 | wc.id(entries.size());
36 | for (int i : entries)
37 | wc.id(i);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBValue.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | /**
4 | * Yup, it's a microclass. Deal with it.
5 | */
6 | public final class DMBValue {
7 | // Constants named with help from dmdism, but not all names match.
8 | /** no value */
9 | public static final int NULL = 0;
10 | /** StringID */
11 | public static final int STRING = 6;
12 | /** MobTypeID */
13 | public static final int MOB_TYPEPATH = 8;
14 | /** ClassID */
15 | public static final int OBJ_TYPEPATH = 9;
16 | /** ClassID */
17 | public static final int TURF_TYPEPATH = 10;
18 | /** ClassID */
19 | public static final int AREA_TYPEPATH = 11;
20 | /** CacheFileID */
21 | public static final int RESOURCE = 12;
22 | /** ClassID */
23 | public static final int DATUM_TYPEPATH = 32;
24 | /** (no value?) */
25 | public static final int SAVEFILE_TYPEPATH = 36;
26 | /** (no value?) */
27 | public static final int FILE_TYPEPATH = 39;
28 | /** (no value?) */
29 | public static final int LIST_TYPEPATH = 40;
30 | /** float */
31 | public static final int NUMBER = 42;
32 | /** InstanceID */
33 | public static final int INSTANCE_TYPEPATH = 41;
34 | /** ClassID */
35 | public static final int IMAGE_TYPEPATH = 63;
36 |
37 | public static final DMBValue NULL_VALUE = new DMBValue(0, 0);
38 |
39 | public final int type;
40 | public final int value;
41 |
42 | public DMBValue(int t, int v) {
43 | type = t;
44 | value = v;
45 | }
46 |
47 | /**
48 | * If the DMBValue is a NUMBER, this gets the resulting value.
49 | */
50 | public float getFloatValue() {
51 | return Float.intBitsToFloat(value);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return (type * 0x100) + value;
57 | }
58 |
59 | @Override
60 | public boolean equals(Object obj) {
61 | if (!(obj instanceof DMBValue))
62 | return false;
63 | DMBValue dv = (DMBValue) obj;
64 | if (dv.type != type)
65 | return false;
66 | if (dv.value != value)
67 | return false;
68 | return true;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/DMBVarTable.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.PrintStream;
4 |
5 | import after.annotations.StringID;
6 | import after.io.framework.DMBReadContext;
7 | import after.io.framework.DMBWriteContext;
8 |
9 | public class DMBVarTable extends DMBObjectEntryBasedSubblock {
10 | // GEN & LHS >= 512, ListID
11 | public int unk;
12 |
13 | @Override
14 | public Entry make() {
15 | return new Entry();
16 | }
17 |
18 | @Override
19 | public void read(DMBReadContext rc) {
20 | super.read(rc);
21 | if (rc.vGEN >= 512 && rc.vLHS >= 512)
22 | unk = rc.io.getInt();
23 | }
24 |
25 | @Override
26 | public void write(DMBWriteContext wc) {
27 | super.write(wc);
28 | if (wc.vGEN >= 512 && wc.vLHS >= 512)
29 | wc.i32(unk);
30 | }
31 |
32 | public static class Entry implements DMBObjectEntryBasedSubblock.Entry {
33 | public DMBValue value = DMBValue.NULL_VALUE;
34 | @StringID
35 | public int name;
36 |
37 | @Override
38 | public void read(DMBReadContext rc) {
39 | int t = rc.io.get();
40 | int v = rc.io.getInt();
41 | value = new DMBValue(t, v);
42 | name = rc.id();
43 | }
44 |
45 | @Override
46 | public void write(DMBWriteContext wc) {
47 | wc.i8(value.type);
48 | wc.i32(value.value);
49 | wc.id(name);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/RADEntry.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.function.Supplier;
6 |
7 | import aedium.NonNull;
8 | import after.io.framework.ReadContext;
9 | import after.io.framework.WriteContext;
10 |
11 | /**
12 | * Random-access datafile entry.
13 | */
14 | public class RADEntry {
15 | public Content content;
16 |
17 | @Override
18 | public String toString() {
19 | return content.toString();
20 | }
21 |
22 | public void read(ReadContext rc, Supplier validContent) {
23 | byte[] data = new byte[rc.i32()];
24 | byte type = rc.i8();
25 | // System.out.println(data.length + ";" + type);
26 | rc.bytes(data);
27 | if (type == 1) {
28 | content = validContent.get();
29 | } else {
30 | content = new InvalidContent();
31 | }
32 | content.read(new ReadContext() {
33 | int ptr = 0;
34 | @Override
35 | public byte i8() {
36 | return data[ptr++];
37 | }
38 |
39 | @Override
40 | public void bytes(byte[] group) {
41 | System.arraycopy(data, ptr, group, 0, group.length);
42 | ptr += group.length;
43 | }
44 | }, data.length);
45 | }
46 |
47 | public void write(WriteContext wc) {
48 | ByteArrayOutputStream store = new ByteArrayOutputStream();
49 | content.write(new WriteContext() {
50 | @Override
51 | public void i8(int value) {
52 | store.write(value);
53 | }
54 |
55 | @Override
56 | public void bytes(byte[] data) {
57 | store.write(data, 0, data.length);
58 | }
59 | });
60 | byte[] data = store.toByteArray();
61 | wc.i32(data.length);
62 | wc.i8((content instanceof InvalidContent) ? 0 : 1);
63 | wc.bytes(data);
64 | }
65 |
66 | public interface Content {
67 | void read(ReadContext rc, int size);
68 | void write(WriteContext wc);
69 | }
70 |
71 | /**
72 | * Represents invalid data that may be recoverable or may be completely corrupt.
73 | */
74 | public class InvalidContent implements Content {
75 | @NonNull
76 | public byte[] data = new byte[0];
77 |
78 | @Override
79 | public String toString() {
80 | return "deleted (" + data.length + " bytes)";
81 | }
82 |
83 | @Override
84 | public void read(ReadContext rc, int size) {
85 | data = new byte[size];
86 | rc.bytes(data);
87 | }
88 |
89 | @Override
90 | public void write(WriteContext wc) {
91 | wc.bytes(data);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/RSCEntryContent.java:
--------------------------------------------------------------------------------
1 | package after.io;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.function.Supplier;
6 |
7 | import aedium.NonNull;
8 | import after.io.framework.ReadContext;
9 | import after.io.framework.WriteContext;
10 |
11 | /**
12 | * There shouldn't be anything else, but there COULD be.
13 | */
14 | public class RSCEntryContent implements RADEntry.Content {
15 | public static final Supplier SUPPLIER = () -> {
16 | return new RSCEntryContent();
17 | };
18 |
19 | @NonNull
20 | public CacheID id = CacheID.ZERO;
21 |
22 | public int time = 0;
23 | public int originalTime = 0;
24 |
25 | @NonNull
26 | public byte[] name = new byte[0];
27 | @NonNull
28 | public byte[] data = new byte[0];
29 |
30 | @Override
31 | public String toString() {
32 | return id + " " + new String(name, StandardCharsets.UTF_8);
33 | }
34 |
35 | @Override
36 | public void read(ReadContext rc, int size) {
37 | byte type = rc.i8();
38 | int uid = rc.i32();
39 | id = new CacheID(uid, type);
40 | time = rc.i32();
41 | originalTime = rc.i32();
42 | data = new byte[rc.i32()];
43 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
44 | while (true) {
45 | byte b = rc.i8();
46 | if (b == 0)
47 | break;
48 | baos.write(b);
49 | }
50 | name = baos.toByteArray();
51 | // System.out.println("RSC file: " + baos.toString() + "#" + baos.size() + "[" + data.length + "]");
52 | rc.bytes(data);
53 | }
54 |
55 | @Override
56 | public void write(WriteContext wc) {
57 | wc.i8(id.typeOrSomething);
58 | wc.i32(id.uniqueID);
59 | wc.i32(time);
60 | wc.i32(originalTime);
61 | wc.i32(data.length);
62 | wc.bytes(name);
63 | wc.i8(0);
64 | wc.bytes(data);
65 | }
66 | }
--------------------------------------------------------------------------------
/after/src/main/java/after/io/framework/DMBContext.java:
--------------------------------------------------------------------------------
1 | package after.io.framework;
2 |
3 | public abstract class DMBContext {
4 | public boolean debug;
5 |
6 | // if you don't want to know, take the blue pill. you'll forget all about this.
7 | // but if you take the red pill, you'll see how deep this versioning problem really goes...
8 | // *link to DMB.md*
9 | public int vGEN, vLHS, vRHS;
10 |
11 | public int basePointer;
12 | public boolean largeObjectIDs;
13 |
14 | public DMBContext(boolean dbg) {
15 | debug = dbg;
16 | }
17 |
18 | abstract protected int position();
19 |
20 | public void section(String text) {
21 | if (debug)
22 | System.out.println(text + " @ 0x" + Integer.toHexString(position()));
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/framework/DMBReadContext.java:
--------------------------------------------------------------------------------
1 | package after.io.framework;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.ByteOrder;
5 |
6 | /**
7 | * Read context.
8 | */
9 | public class DMBReadContext extends DMBContext implements ReadContext {
10 | public ByteBuffer io;
11 |
12 | public DMBReadContext(byte[] data, boolean dbg) {
13 | super(dbg);
14 |
15 | io = ByteBuffer.wrap(data);
16 | io.order(ByteOrder.LITTLE_ENDIAN);
17 | }
18 |
19 | @Override
20 | public int position() {
21 | return io.position();
22 | }
23 |
24 | public int remaining() {
25 | return io.capacity() - position();
26 | }
27 |
28 | @Override
29 | public byte i8() {
30 | return io.get();
31 | }
32 |
33 | public int id() {
34 | if (largeObjectIDs)
35 | return i32();
36 | return i16() & 0xFFFF;
37 | }
38 |
39 | public void ids(int[] group) {
40 | for (int i = 0; i < group.length; i++)
41 | group[i] = id();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/framework/DMBWriteContext.java:
--------------------------------------------------------------------------------
1 | package after.io.framework;
2 |
3 | /**
4 | * Write context.
5 | */
6 | public abstract class DMBWriteContext extends DMBContext implements WriteContext {
7 | public int position = 0;
8 |
9 | public DMBWriteContext(boolean dbg) {
10 | super(dbg);
11 | }
12 |
13 | @Override
14 | protected int position() {
15 | return position;
16 | }
17 |
18 | protected abstract void i8Internal(int value);
19 |
20 | @Override
21 | public void i8(int i) {
22 | i8Internal(i);
23 | position++;
24 | }
25 |
26 | public void id(int i) {
27 | if (largeObjectIDs) {
28 | i32(i);
29 | } else {
30 | i16(i);
31 | }
32 | }
33 | public void ids(int[] it) {
34 | for (int i : it)
35 | id(i);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/framework/ReadContext.java:
--------------------------------------------------------------------------------
1 | package after.io.framework;
2 |
3 | public interface ReadContext {
4 | byte i8();
5 |
6 | default short i16() {
7 | int a = i8() & 0xFF;
8 | return (short) (((i8() & 0xFF) << 8) | a);
9 | }
10 |
11 | default int i32() {
12 | int a = i16() & 0xFFFF;
13 | return (((i16() & 0xFFFF) << 16) | a);
14 | }
15 |
16 | default float f32() {
17 | return Float.intBitsToFloat(i32());
18 | }
19 |
20 | default void ints(int[] group) {
21 | for (int i = 0; i < group.length; i++)
22 | group[i] = i32();
23 | }
24 |
25 | default void floats(float[] group) {
26 | for (int i = 0; i < group.length; i++)
27 | group[i] = f32();
28 | }
29 |
30 | default void bytes(byte[] group) {
31 | for (int i = 0; i < group.length; i++)
32 | group[i] = i8();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/after/src/main/java/after/io/framework/WriteContext.java:
--------------------------------------------------------------------------------
1 | package after.io.framework;
2 |
3 | public interface WriteContext {
4 | void i8(int value);
5 |
6 | default void i16(int value) {
7 | i8(value);
8 | i8(value >> 8);
9 | }
10 |
11 | default void i32(int value) {
12 | i16(value);
13 | i16(value >> 16);
14 | }
15 |
16 | default void f32(float value) {
17 | i32(Float.floatToRawIntBits(value));
18 | }
19 |
20 | default void ints(int[] it) {
21 | for (int i : it)
22 | i32(i);
23 | }
24 |
25 | default void floats(float[] it) {
26 | for (float i : it)
27 | f32(i);
28 | }
29 |
30 | default void bytes(byte[] data) {
31 | for (byte b : data)
32 | i8(b);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/after/src/main/java/after/net/PacketFrame.java:
--------------------------------------------------------------------------------
1 | package after.net;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.io.OutputStream;
7 | import java.nio.ByteBuffer;
8 | import java.nio.ByteOrder;
9 |
10 | /**
11 | * This is the framing of a packet.
12 | * This doesn't handle encryption, on purpose.
13 | * It's also not thread-safe, even for 'thread-safe-looking' operations.
14 | */
15 | public class PacketFrame {
16 | /**
17 | * If this packet frame has a sequence number.
18 | * (non-handshake client packets only)
19 | */
20 | public boolean hasSeq;
21 |
22 | /**
23 | * Sequence number.
24 | */
25 | public short sequence;
26 |
27 | /**
28 | * The type of packet.
29 | */
30 | public short type;
31 |
32 | /**
33 | * The data. The position in the buffer indicates the length of the data.
34 | */
35 | public final ByteBuffer data = ByteBuffer.allocate(0xFFFF);
36 |
37 | /**
38 | * The backing array of the data buffer.
39 | */
40 | public final byte[] dataArray = data.array();
41 |
42 | /**
43 | * Array in which a written packet is constructed.
44 | * (There's reasons for this, really...)
45 | */
46 | private final byte[] writeDataArray = new byte[0x10005];
47 |
48 | public PacketFrame() {
49 | data.order(ByteOrder.LITTLE_ENDIAN);
50 | }
51 |
52 | public void read(DataInputStream dis) throws IOException {
53 | if (hasSeq)
54 | sequence = dis.readShort();
55 | type = dis.readShort();
56 | int len = dis.readUnsignedShort();
57 | dis.readFully(dataArray, 0, len);
58 | data.position(len);
59 | }
60 |
61 | public void write(OutputStream dos) throws IOException {
62 | int ptr = 0;
63 | // This is another one of those "read-sensitive" applications.
64 | // In practice this means shenanigans.
65 | if (hasSeq) {
66 | writeDataArray[ptr++] = (byte) (sequence >> 8);
67 | writeDataArray[ptr++] = (byte) sequence;
68 | }
69 | writeDataArray[ptr++] = (byte) (type >> 8);
70 | writeDataArray[ptr++] = (byte) type;
71 | int len = data.position();
72 | writeDataArray[ptr++] = (byte) (len >> 8);
73 | writeDataArray[ptr++] = (byte) len;
74 | System.arraycopy(dataArray, 0, writeDataArray, ptr, len);
75 | ptr += len;
76 | // And now commit.
77 | dos.write(writeDataArray, 0, ptr);
78 | }
79 |
80 | public void writeWebSocket(DataOutputStream dos) throws IOException {
81 | writeWebSocketInternal(dos, type, dataArray, 0, data.position());
82 | }
83 |
84 | public static void writeWebSocketInternal(DataOutputStream dos, short type, byte[] data, int offset, int length) throws IOException {
85 | // FIN, Binary
86 | dos.write(0x82);
87 | int pktLen = length + 2;
88 | if (pktLen < 126) {
89 | dos.write(pktLen);
90 | } else if (pktLen < 65536) {
91 | dos.write(126);
92 | dos.writeShort(pktLen);
93 | } else {
94 | dos.write(0x7F);
95 | dos.writeLong(length + 2);
96 | }
97 | dos.writeShort(type);
98 | dos.write(data, offset, length);
99 | }
100 |
101 | public void begin(int t) {
102 | data.position(0);
103 | type = (short) t;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/after/src/test/java/after/tests/AlgorithmTest.java:
--------------------------------------------------------------------------------
1 | package after.tests;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import org.junit.Test;
6 |
7 | import after.algorithms.CYCSUB;
8 | import after.algorithms.NQCRC;
9 | import after.algorithms.RUNSUB;
10 | import after.algorithms.XORJump9;
11 |
12 | /**
13 | * Acts as a set of test vectors for various algorithms.
14 | */
15 | public class AlgorithmTest {
16 | @Test
17 | public void testNQCRC() {
18 | assert NQCRC.hash(0, "The quick brown fox jumps over the lazy dog.\n".getBytes(StandardCharsets.UTF_8)) == 0x93fe309a;
19 | assert NQCRC.hash(0, "3\n".getBytes(StandardCharsets.UTF_8)) == 0xda08bd33;
20 | }
21 |
22 | @Test
23 | public void testXORJUMP9() {
24 | byte[] bloop = new byte[] {(byte) 0x00, (byte) 0x80, (byte) 0x40};
25 | assert XORJump9.xorJump9(bloop, (byte) 0x40) == 0x5B;
26 | assert bloop[0] == 0x40;
27 | assert bloop[1] == (byte) 0xC9;
28 | assert bloop[2] == 0x12;
29 | }
30 |
31 | @Test
32 | public void testRUNSUB() {
33 | byte[] data = new byte[] {(byte) 0xf8, 0x38, 0x78, (byte) 0xb8, (byte) 0xc2, 0x3b, 0x0a};
34 | byte[] dataCopy = new byte[data.length];
35 | System.arraycopy(data, 0, dataCopy, 0, data.length);
36 |
37 | int key = 0xa374c5b8;
38 |
39 | // Decrypt
40 | RUNSUB.decrypt(data, 0, data.length, key);
41 | assert data[0] == '@';
42 | assert data[1] == '@';
43 | assert data[2] == '@';
44 | assert data[3] == '@';
45 | assert data[4] == '\n';
46 | assert data[5] == 0;
47 |
48 | // Encrypt again and compare to the original
49 | RUNSUB.encrypt(data, 0, data.length, key);
50 | for (int i = 0; i < data.length; i++)
51 | assert data[i] == dataCopy[i];
52 | }
53 |
54 | @Test
55 | public void testCYCSUB() {
56 | byte[] data = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05};
57 | byte[] key = new byte[] {0x10, 0x20, 0x1F};
58 | byte[] dataCopy = new byte[data.length];
59 | System.arraycopy(data, 0, dataCopy, 0, data.length);
60 |
61 | // Encrypt
62 | CYCSUB.encrypt(data, 0, data.length, key, 0, key.length);
63 |
64 | // Decrypt again and compare to the original
65 | CYCSUB.decrypt(data, 0, data.length, key, 0, key.length);
66 | for (int i = 0; i < data.length; i++)
67 | assert data[i] == dataCopy[i];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/after/src/test/java/after/tests/EdenTest.java:
--------------------------------------------------------------------------------
1 | package after.tests;
2 |
3 | import java.io.FileOutputStream;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | import org.junit.Test;
7 |
8 | import after.Eden;
9 | import after.io.DMB;
10 | import after.io.DMBProcTable;
11 | import after.io.framework.DMBReadContext;
12 | import after.io.framework.DMBWriteContext;
13 |
14 | /**
15 | * This is an ongoing project to create an "eden file", a completely non-BYOND-created DMB file.
16 | * It will be considered successful when BYOND is capable of loading and running the DMB.
17 | * I've gotten to that point, so there's also a little side project to add a verb to that.
18 | */
19 | public class EdenTest {
20 | @Test
21 | public void runEdenTest() throws Exception {
22 | DMB dmb = Eden.newEden();
23 | // ---
24 | put(dmb, "eden.dmb");
25 | // ---
26 |
27 | // verb table
28 | int lVT = dmb.lists.add(new int[] {0});
29 | // verb code
30 | // GETVAR src
31 | // PUSHVAL ">:D"
32 | // OUTPUT
33 | // END
34 | int lVC = dmb.lists.add(new int[] {0x33, 0xFFCE, 0x60, 0x06, dmb.strings.of(">:D"), 0x03, 0});
35 |
36 | DMBProcTable.Entry testProc = new DMBProcTable.Entry();
37 | testProc.name = dmb.strings.of(">:D");
38 | testProc.verbCategory = dmb.strings.of(">:D");
39 | testProc.code = lVC;
40 | dmb.procs.add(testProc);
41 |
42 | dmb.classes.entries.get(dmb.standardObjectIDs.client).verbTable = lVT;
43 |
44 | dmb.fixStrings();
45 | // ---
46 | put(dmb, "eden.ext.dmb");
47 | }
48 |
49 | private static void put(DMB dmb, String where) throws Exception {
50 | FileOutputStream putEden = new FileOutputStream(where);
51 | dmb.write(new DMBWriteContext(true) {
52 | @Override
53 | protected void i8Internal(int value) {
54 | try {
55 | putEden.write(value);
56 | } catch (Exception e) {
57 | throw new RuntimeException(e);
58 | }
59 | }
60 | });
61 | putEden.close();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/after/src/test/java/after/tests/EquivalenceTest.java:
--------------------------------------------------------------------------------
1 | package after.tests;
2 |
3 | import java.io.File;
4 | import java.nio.file.Files;
5 | import java.nio.file.Path;
6 | import java.nio.file.Paths;
7 | import java.util.Collection;
8 | import java.util.LinkedList;
9 | import java.util.concurrent.atomic.AtomicInteger;
10 |
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.junit.runners.Parameterized;
14 |
15 | import after.io.DMB;
16 | import after.io.framework.DMBReadContext;
17 | import after.io.framework.DMBWriteContext;
18 |
19 | /**
20 | * Performs a general battery of sanity tests given source test subjects.
21 | */
22 | @RunWith(Parameterized.class)
23 | public class EquivalenceTest {
24 | @Parameterized.Parameters(name = "{0}")
25 | public static Collection