diff --git a/.github/workflows/go-ossf-slsa3-publish.yml b/.github/workflows/go-ossf-slsa3-publish.yml index 86a8aaa..48af5b3 100644 --- a/.github/workflows/go-ossf-slsa3-publish.yml +++ b/.github/workflows/go-ossf-slsa3-publish.yml @@ -29,7 +29,7 @@ jobs: id-token: write # To sign. contents: write # To upload release assets. actions: read # To read workflow path. - uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v1.10.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v2.0.0 with: go-version: 1.18 config-file: ".slsa-goreleaser.yml" @@ -41,7 +41,7 @@ jobs: id-token: write # To sign. contents: write # To upload release assets. actions: read # To read workflow path. - uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v1.10.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v2.0.0 with: go-version: 1.18 config-file: ".slsa-goreleaser-linux.yml" diff --git a/.gitignore b/.gitignore index 96721bf..fdfa581 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ TTSModManager.exe bash.exe.stackdump +oryxBuildBinary diff --git a/file/conversions.go b/file/conversions.go index 0e0a7a0..d5d1f4d 100644 --- a/file/conversions.go +++ b/file/conversions.go @@ -59,6 +59,36 @@ func ForceParseIntoStrArray(m *types.J, k string, dest *[]string) error { return nil } +func TryParseIntoStrMap(m *types.J, k string, dest *map[string]string) { + _ = ForceParseIntoStrMap(m, k, dest) +} + +func ForceParseIntoStrMap(m *types.J, k string, dest *map[string]string) error { + raw, ok := (*m)[k] + if !ok { + return fmt.Errorf("key %s not found", k) + } + if rawmapstrings, ok := raw.(map[string]string); ok { + *dest = rawmapstrings + delete((*m), k) + return nil + } + if rawmap, ok := raw.(map[string]any); ok { + massaged := map[string]string{} + for k, v := range rawmap { + vstr, ok := v.(string) + if !ok { + return fmt.Errorf("m[%s] of type %T, not string", k, v) + } + massaged[k] = vstr + } + *dest = massaged + delete((*m), k) + return nil + } + return fmt.Errorf("key %s was not type map[string]string, was %T", k, raw) +} + // TryParseIntoInt will not throw error func TryParseIntoInt(m *types.J, k string, dest *int64) { _ = ForceParseIntoInt(m, k, dest) diff --git a/mod/generate_test.go b/mod/generate_test.go index 66fff21..36204f1 100644 --- a/mod/generate_test.go +++ b/mod/generate_test.go @@ -54,6 +54,147 @@ func TestGenerate(t *testing.T) { "DecalPallet": nil, }, }, + { + name: "Object State recursive", + inputRoot: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates_order": []interface{}{"parent"}, + }, + inputLuaSrc: map[string]string{ + "parent/eda22b/childstate2.ttslua": "var foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\n", + }, + inputOjbs: map[string]types.J{ + "parent.json": map[string]interface{}{ + "GUID": "parent", + "States_path": map[string]interface{}{ + "2": "eda22b", + }, + "ContainedObjects_path": "parent", + }, + "parent/eda22b.json": map[string]interface{}{ + "GUID": "eda22b", + "Autoraise": true, + "ContainedObjects_path": "eda22b", + "ContainedObjects_order": []string{"childstate2"}, + }, + "parent/eda22b/childstate2.json": map[string]interface{}{ + "Description": "child of state 2", + "GUID": "childstate2", + "LuaScript_path": "parent/eda22b/childstate2.ttslua", + }, + }, + want: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates": []any{ + map[string]any{ + "GUID": "parent", + "States": map[string]interface{}{ + "2": map[string]interface{}{ + "Autoraise": true, + "GUID": "eda22b", + "ContainedObjects": []any{ + map[string]any{ + "Description": "child of state 2", + "GUID": "childstate2", + "LuaScript": "var foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\n", + }, + }, + }, + }, + }, + }, + "CameraStates": nil, + "ComponentTags": nil, + "MusicPlayer": nil, + "Sky": "", + "TabStates": nil, + "Note": "", + "Table": "", + "LuaScript": "", + "LuaScriptState": "", + "SnapPoints": nil, + "XmlUI": "", + "Turns": nil, + "VersionNumber": "", + "GameMode": "", + "GameType": "", + "Hands": nil, + "Grid": nil, + "Lighting": nil, + "GameComplexity": "", + "Decals": nil, + "CustomUIAssets": nil, + "DecalPallet": nil, + }, + }, + { + name: "Object State and contained object generation", + inputRoot: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates_order": []interface{}{"parent"}, + }, + inputLuaSrc: map[string]string{}, + inputOjbs: map[string]types.J{ + "parent.json": map[string]interface{}{ + "GUID": "parent", + "States_path": map[string]interface{}{ + "2": "state2", + }, + "ContainedObjects_path": "parent", + "ContainedObjects_order": []string{"co123"}, + }, + "parent/state2.json": map[string]interface{}{ + "GUID": "state2", + "Autoraise": true, + }, + "parent/co123.json": map[string]interface{}{ + "Description": "contained object", + "GUID": "co123", + }, + }, + want: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates": []any{ + map[string]any{ + "GUID": "parent", + "States": map[string]interface{}{ + "2": map[string]interface{}{ + "Autoraise": true, + "GUID": "state2", + }, + }, + "ContainedObjects": []any{ + map[string]any{ + "GUID": "co123", + "Description": "contained object", + }, + }, + }, + }, + "CameraStates": nil, + "ComponentTags": nil, + "MusicPlayer": nil, + "Sky": "", + "TabStates": nil, + "Note": "", + "Table": "", + "LuaScript": "", + "LuaScriptState": "", + "SnapPoints": nil, + "XmlUI": "", + "Turns": nil, + "VersionNumber": "", + "GameMode": "", + "GameType": "", + "Hands": nil, + "Grid": nil, + "Lighting": nil, + "GameComplexity": "", + "Decals": nil, + "CustomUIAssets": nil, + "DecalPallet": nil, + }, + }, } { t.Run(tc.name, func(t *testing.T) { rootff := &tests.FakeFiles{ diff --git a/mod/reverse_test.go b/mod/reverse_test.go index caa3c21..2812daf 100644 --- a/mod/reverse_test.go +++ b/mod/reverse_test.go @@ -93,37 +93,37 @@ func TestReverse(t *testing.T) { }, wantObjs: map[string]types.J{}, wantModSettings: map[string]types.J{ - "SnapPoints.json": types.J{ + "SnapPoints.json": { "testarray": []map[string]interface{}{ // implementation detail of fake files - map[string]interface{}{ + { "Position": types.J{ "x": float64(12.123), "y": float64(22.123), "z": float64(32.123), }, }, - map[string]interface{}{ + { "Position": types.J{ "x": float64(12.123), "y": float64(22.123), "z": float64(32.123), }, }, - map[string]interface{}{ + { "Position": types.J{ "x": float64(12.123), "y": float64(22.123), "z": float64(32.123), }, }, - map[string]interface{}{ + { "Position": types.J{ "x": float64(12.123), "y": float64(22.123), "z": float64(32.123), }, }, - map[string]interface{}{ + { "Position": types.J{ "x": float64(12.123), "y": float64(22.123), @@ -159,7 +159,7 @@ func TestReverse(t *testing.T) { }, wantObjs: map[string]types.J{}, wantModSettings: map[string]types.J{}, - wantSrcTexts: map[string]string{ + wantSrcTexts: map[string]string{ "playermat/SkillToken.ttslua": "MIN_VALUE = -99\r\nMAX_VALUE = 999\r\n\r\nfunction onload(saved_data)\r\n light_mode = false\r\n val = 0\r\n\r\n if saved_data ~= \"\" then\r\n local loaded_data = JSON.decode(saved_data)\r\n light_mode = loaded_data[1]\r\n val = loaded_data[2]\r\n end\r\n\r\n createAll()\r\nend\r\n\r\nfunction updateSave()\r\n local data_to_save = {light_mode, val}\r\n saved_data = JSON.encode(data_to_save)\r\n self.script_state = saved_data\r\nend\r\n\r\nfunction createAll()\r\n s_color = {0.5, 0.5, 0.5, 95}\r\n\r\n if light_mode then\r\n f_color = {1,1,1,95}\r\n else\r\n f_color = {0,0,0,100}\r\n end\r\n\r\n\r\n\r\n self.createButton({\r\n label=tostring(val),\r\n click_function=\"add_subtract\",\r\n function_owner=self,\r\n position={0,0.05,0},\r\n height=600,\r\n width=1000,\r\n alignment = 3,\r\n scale={x=1.5, y=1.5, z=1.5},\r\n font_size=600,\r\n font_color=f_color,\r\n color={0,0,0,0}\r\n })\r\n\r\n\r\n\r\n\r\n if light_mode then\r\n lightButtonText = \"[ Set dark ]\"\r\n else\r\n lightButtonText = \"[ Set light ]\"\r\n end\r\n\r\nend\r\n\r\nfunction removeAll()\r\n self.removeInput(0)\r\n self.removeInput(1)\r\n self.removeButton(0)\r\n self.removeButton(1)\r\n self.removeButton(2)\r\nend\r\n\r\nfunction reloadAll()\r\n removeAll()\r\n createAll()\r\n\r\n updateSave()\r\nend\r\n\r\nfunction swap_fcolor(_obj, _color, alt_click)\r\n light_mode = not light_mode\r\n reloadAll()\r\nend\r\n\r\nfunction swap_align(_obj, _color, alt_click)\r\n center_mode = not center_mode\r\n reloadAll()\r\nend\r\n\r\nfunction editName(_obj, _string, value)\r\n self.setName(value)\r\n setTooltips()\r\nend\r\n\r\nfunction add_subtract(_obj, _color, alt_click)\r\n mod = alt_click and -1 or 1\r\n new_value = math.min(math.max(val + mod, MIN_VALUE), MAX_VALUE)\r\n if val ~= new_value then\r\n val = new_value\r\n updateVal()\r\n updateSave()\r\n end\r\nend\r\n\r\nfunction updateVal()\r\n\r\n self.editButton({\r\n index = 0,\r\n label = tostring(val),\r\n\r\n })\r\nend\r\n\r\nfunction reset_val()\r\n val = 0\r\n updateVal()\r\n updateSave()\r\nend\r\n\r\nfunction setTooltips()\r\n self.editInput({\r\n index = 0,\r\n value = self.getName(),\r\n tooltip = ttText\r\n })\r\n self.editButton({\r\n index = 0,\r\n value = tostring(val),\r\n tooltip = ttText\r\n })\r\nend\r\n\r\nfunction null()\r\nend\r\n\r\nfunction keepSample(_obj, _string, value)\r\n reloadAll()\r\nend", }, wantObjTexts: map[string]string{}, @@ -175,7 +175,7 @@ func TestReverse(t *testing.T) { wantObjs: map[string]types.J{}, wantSrcTexts: map[string]string{}, wantModSettings: map[string]types.J{}, - wantObjTexts: map[string]string{ + wantObjTexts: map[string]string{ "LuaScript.ttslua": "var foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\n", }, }, @@ -192,7 +192,7 @@ func TestReverse(t *testing.T) { }, wantObjs: map[string]types.J{}, wantModSettings: map[string]types.J{}, - wantObjTexts: map[string]string{ + wantObjTexts: map[string]string{ "LuaScript.ttslua": "require(\"playermat/SkillToken\")\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42", }, }, @@ -237,6 +237,190 @@ func TestReverse(t *testing.T) { wantObjTexts: map[string]string{}, wantSrcTexts: map[string]string{}, }, + { + name: "State tracking - 2", + input: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates": []map[string]interface{}{ + { + "GUID": "parent", + "States": map[string]interface{}{ + "2": map[string]interface{}{ + "Autoraise": true, + "GUID": "eda22b", + }, + }, + }, + }, + }, + wantRootConfig: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates_order": []interface{}{"parent"}, + }, + wantModSettings: map[string]types.J{}, + wantObjs: map[string]types.J{ + "parent.json": map[string]interface{}{ + "GUID": "parent", + "States_path": map[string]string{ + "2": "eda22b", + }, + "ContainedObjects_path": "parent", + }, + "parent/eda22b.json": map[string]interface{}{ + "GUID": "eda22b", + "Autoraise": true, + }, + }, + wantObjTexts: map[string]string{}, + wantSrcTexts: map[string]string{}, + }, + { + name: "State tracking - 3", + input: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates": []map[string]interface{}{ + { + "GUID": "parent", + "States": map[string]interface{}{ + "2": map[string]interface{}{ + "Autoraise": true, + "GUID": "eda22b", + }, + "3": map[string]interface{}{ + "Autoraise": false, + "GUID": "child3", + }, + }, + }, + }, + }, + wantRootConfig: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates_order": []interface{}{"parent"}, + }, + wantModSettings: map[string]types.J{}, + wantObjs: map[string]types.J{ + "parent.json": map[string]interface{}{ + "GUID": "parent", + "States_path": map[string]string{ + "2": "eda22b", + "3": "child3", + }, + "ContainedObjects_path": "parent", + }, + "parent/eda22b.json": map[string]interface{}{ + "GUID": "eda22b", + "Autoraise": true, + }, + "parent/child3.json": map[string]interface{}{ + "GUID": "child3", + "Autoraise": false, + }, + }, + wantObjTexts: map[string]string{}, + wantSrcTexts: map[string]string{}, + }, + { + name: "State tracking and sub objects", + input: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates": []map[string]interface{}{ + { + "GUID": "parent", + "States": map[string]interface{}{ + "2": map[string]interface{}{ + "Autoraise": true, + "GUID": "state2", + }, + }, + "ContainedObjects": []any{ + map[string]any{ + "Description": "contained object", + "GUID": "co123", + }, + }, + }, + }, + }, + wantRootConfig: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates_order": []interface{}{"parent"}, + }, + wantModSettings: map[string]types.J{}, + wantObjs: map[string]types.J{ + "parent.json": map[string]interface{}{ + "GUID": "parent", + "States_path": map[string]string{ + "2": "state2", + }, + "ContainedObjects_path": "parent", + "ContainedObjects_order": []string{"co123"}, + }, + "parent/state2.json": map[string]interface{}{ + "GUID": "state2", + "Autoraise": true, + }, + "parent/co123.json": map[string]interface{}{ + "GUID": "co123", + "Description": "contained object", + }, + }, + wantObjTexts: map[string]string{}, + wantSrcTexts: map[string]string{}, + }, + { + name: "State tracking - checking recursion", + input: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates": []map[string]interface{}{ + { + "GUID": "parent", + "States": map[string]interface{}{ + "2": map[string]interface{}{ + "Autoraise": true, + "GUID": "eda22b", + "ContainedObjects": []any{ + map[string]any{ + "Description": "child of state 2", + "GUID": "childstate2", + "LuaScript": "var foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\n", + }, + }, + }, + }, + }, + }, + }, + wantRootConfig: map[string]interface{}{ + "SaveName": "cool mod", + "ObjectStates_order": []interface{}{"parent"}, + }, + wantModSettings: map[string]types.J{}, + wantObjs: map[string]types.J{ + "parent.json": map[string]interface{}{ + "GUID": "parent", + "States_path": map[string]string{ + "2": "eda22b", + }, + "ContainedObjects_path": "parent", + }, + "parent/eda22b.json": map[string]interface{}{ + "GUID": "eda22b", + "Autoraise": true, + "ContainedObjects_path": "eda22b", + "ContainedObjects_order": []string{"childstate2"}, + }, + "parent/eda22b/childstate2.json": map[string]interface{}{ + "Description": "child of state 2", + "GUID": "childstate2", + "LuaScript_path": "parent/eda22b/childstate2.ttslua", + }, + }, + wantSrcTexts: map[string]string{}, + wantObjTexts: map[string]string{ + "parent/eda22b/childstate2.ttslua": "var foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\nvar foo = 42\n", + }, + }, } { t.Run(tc.name, func(t *testing.T) { finalOutput := tests.NewFF() diff --git a/objects/objects.go b/objects/objects.go index 05eb52e..6160fc7 100644 --- a/objects/objects.go +++ b/objects/objects.go @@ -18,7 +18,9 @@ type objConfig struct { gmnotesPath string subObjDir string subObjOrder []string // array of base filenames of subobjects + stateNames map[string]string subObj []*objConfig + states map[string]*objConfig } func (o *objConfig) parseFromFile(filepath string, j file.JSONReader) error { @@ -41,6 +43,16 @@ func (o *objConfig) parseFromFile(filepath string, j file.JSONReader) error { } o.subObj = append(o.subObj, subo) } + for stateID, oname := range o.stateNames { + stateO := &objConfig{} + relFilename := path.Join(path.Dir(filepath), o.subObjDir, fmt.Sprintf("%s.json", oname)) + + err = stateO.parseFromFile(relFilename, j) + if err != nil { + return fmt.Errorf("parseFromFile(%s): %v", relFilename, err) + } + o.states[stateID] = stateO + } } return nil } @@ -58,11 +70,14 @@ func (o *objConfig) parseFromJSON(data map[string]interface{}) error { o.guid = guid o.subObj = []*objConfig{} o.subObjOrder = []string{} + o.stateNames = map[string]string{} + o.states = map[string]*objConfig{} file.TryParseIntoStr(&o.data, "LuaScriptState_path", &o.luascriptstatePath) file.TryParseIntoStr(&o.data, "GMNotes_path", &o.gmnotesPath) file.TryParseIntoStr(&o.data, "ContainedObjects_path", &o.subObjDir) file.TryParseIntoStrArray(&o.data, "ContainedObjects_order", &o.subObjOrder) + file.TryParseIntoStrMap(&o.data, "States_path", &o.stateNames) for _, needSmoothing := range []string{"Transform", "ColorDiffuse"} { if v, ok := o.data[needSmoothing]; ok { @@ -84,54 +99,36 @@ func (o *objConfig) parseFromJSON(data map[string]interface{}) error { o.data["AttachedSnapPoints"] = sm } - // apply smoothing to object states if states, ok := o.data["States"]; ok { statesMap, ok := states.(map[string]interface{}) if !ok { - return fmt.Errorf("type mismatch in States : %v", states) + return fmt.Errorf("type mismatch in States (%T): %v", states, states) } for stateName, stateData := range statesMap { stateObj, ok := stateData.(map[string]interface{}) if !ok { return fmt.Errorf("type mismatch in State %s : %v", stateName, stateData) } - - for _, needSmoothing := range []string{"Transform", "ColorDiffuse"} { - if v, ok := stateObj[needSmoothing]; ok { - stateObj[needSmoothing] = Smooth(v) - } - } - - if v, ok := stateObj["AltLookAngle"]; ok { - vv, err := SmoothAngle(v) - if err != nil { - return fmt.Errorf("SmoothAngle(<%s>) in State %s: %v", "AltLookAngle", stateName, err) - } - stateObj["AltLookAngle"] = vv - } - - if sp, ok := stateObj["AttachedSnapPoints"]; ok { - sm, err := SmoothSnapPoints(sp) - if err != nil { - return fmt.Errorf("SmoothSnapPoints(<%s>) in State %s: %v", o.guid, stateName, err) - } - stateObj["AttachedSnapPoints"] = sm + stateO := &objConfig{} + err := stateO.parseFromJSON(stateObj) + if err != nil { + return fmt.Errorf("parseFromJSON(%v): %v", stateObj, err) } - - statesMap[stateName] = stateObj + o.states[stateName] = stateO + o.stateNames[stateName] = stateO.getAGoodFileName() } - o.data["States"] = statesMap } + delete(o.data, "States") if rawObjs, ok := o.data["ContainedObjects"]; ok { rawArr, ok := rawObjs.([]interface{}) if !ok { - return fmt.Errorf("type mismatch in ContainedObjects : %v", rawArr) + return fmt.Errorf("type mismatch in ContainedObjects; want []any got %T", rawObjs) } for _, rawSubO := range rawArr { subO, ok := rawSubO.(map[string]interface{}) if !ok { - return fmt.Errorf("type mismatch in ContainedObjects : %v", rawSubO) + return fmt.Errorf("type mismatch in ContainedObjects; want map[string]any got %T", rawSubO) } so := objConfig{} if err := so.parseFromJSON(subO); err != nil { @@ -198,6 +195,19 @@ func (o *objConfig) print(l, x file.TextReader) (J, error) { if len(subs) > 0 { out["ContainedObjects"] = subs } + + s := map[string]J{} + for name, state := range o.states { + printed, err := state.print(l, x) + if err != nil { + return nil, err + } + s[name] = printed + } + if len(s) > 0 { + out["States"] = s + } + return out, nil } @@ -271,17 +281,20 @@ func (o *objConfig) printToFile(filepath string, p *Printer) error { } // recurse if need be - if o.subObj != nil && len(o.subObj) > 0 { + if len(o.subObj) > 0 || len(o.states) > 0 { subdirName, err := p.Dir.CreateDir(filepath, o.getAGoodFileName()) if err != nil { return fmt.Errorf("<%v>.CreateDir(%s, %s) : %v", o.guid, filepath, o.getAGoodFileName(), err) } out["ContainedObjects_path"] = subdirName o.subObjDir = subdirName + } + + if len(o.subObj) > 0 { for _, subo := range o.subObj { - err = subo.printToFile(path.Join(filepath, subdirName), p) + err = subo.printToFile(path.Join(filepath, o.subObjDir), p) if err != nil { - return fmt.Errorf("printing file %s: %v", path.Join(filepath, subdirName), err) + return fmt.Errorf("printing file %s: %v", path.Join(filepath, o.subObjDir), err) } } if len(o.subObj) != len(o.subObjOrder) { @@ -290,6 +303,19 @@ func (o *objConfig) printToFile(filepath string, p *Printer) error { out["ContainedObjects_order"] = o.subObjOrder } + if len(o.states) > 0 { + for _, state := range o.states { + err = state.printToFile(path.Join(filepath, o.subObjDir), p) + if err != nil { + return fmt.Errorf("printing file %s: %v", path.Join(filepath, o.subObjDir), err) + } + } + if len(o.stateNames) != len(o.states) { + return fmt.Errorf("sub state mismatch for %s", o.getAGoodFileName()) + } + out["States_path"] = o.stateNames + } + // print self fname := path.Join(filepath, o.getAGoodFileName()+".json") return p.J.WriteObj(out, fname) @@ -362,7 +388,8 @@ func (d *db) print(l file.TextReader, x file.TextReader, order []string) (ObjArr // --foo.json (guid=1234) // --bar.json (guid=888) // --888/ -// --baz.json (guid=999) << this is a child of bar.json +// +// --baz.json (guid=999) << this is a child of bar.json func ParseAllObjectStates(l file.TextReader, x file.TextReader, j file.JSONReader, dir file.DirExplorer, order []string) ([]map[string]interface{}, error) { d := db{ j: j,